.NET CORE之Authentication


  這篇文章以實現一個Basic認證來了解下在 .NET CORE 下面如何去實現認證。

  首先可以肯定的是認證實現是基於 Middlerware 管道實現的,官方源碼地址:https://github.com/aspnet/Security。可以看到官方已經實現了jwt、oauth、google等諸多第三方認證,其原理今天我們就不在這里介紹。

    下面我們來實現Basic認證。

  AuthenticationSchemeOptions。 負責初始化參數配置。這里我們額外需要一個驗證用戶的委托。代碼如下:

    public class BasicOption : AuthenticationSchemeOptions
    {
        public BasicOption()
            : base()
        {
            Events = new BasicEvents();
        }

        public Func<string, string, bool> ValidateUser { get; set; }
        public new BasicEvents Events
        {
            get { return (BasicEvents)base.Events; }
            set { base.Events = value; }
        }
    }

  BasicDefault。定義一些基本常量

    public static class BasicDefault
    {
        public const string AuthenticationScheme = "Basic";
        public const string DisplayName = "Basic";
    }

  ResultContext<T>。用於認證流程中上下文擴展

    public class BasicTokenValidatedContext : ResultContext<BasicOption>
    {
        public BasicTokenValidatedContext(HttpContext context, AuthenticationScheme scheme, BasicOption options)
            : base(context, scheme, options)
        {
        }

    }

  BasicEvents。用於認證流程中各類自定義事件觸發,在這里我們定義了一個 驗證成功后事件,用於客戶端自定義設置

    public class BasicEvents
    {
        public Func<BasicTokenValidatedContext, Task> OnTokenValidated { get; set; } = context => Task.CompletedTask;

        public virtual Task TokenValidated(BasicTokenValidatedContext context) => OnTokenValidated(context);
    }

  AuthenticationHandler<T>。這里認證流程中的核心部分,HandleAuthenticateAsync 用於處理認證。HandleChallengeAsync 用於處理認證失敗后續Challenge

    public class BasicHandler : AuthenticationHandler<BasicOption>
    {
        private const string KEY_AUTHORIZATION = "authorization";
        private const string KEY_SPLIT = ":";

        protected new BasicEvents Events
        {
            get => (BasicEvents)base.Events;
            set => base.Events = value;
        }

        public BasicHandler(IOptionsMonitor<BasicOption> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock)
            : base(options, logger, encoder, clock)
        {
        }

        protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
        {
            string authorization = Request.Headers[KEY_AUTHORIZATION];
            if (string.IsNullOrEmpty(authorization))
            {
                Logger.LogInformation("請求頭authorization為空,目標路徑{0}", Request.Path);
                return AuthenticateResult.NoResult();
            }
            string token = string.Empty;
            if (authorization.StartsWith(BasicDefault.AuthenticationScheme + " ", StringComparison.CurrentCultureIgnoreCase))
            {
                token = authorization.Substring(BasicDefault.AuthenticationScheme.Length).Trim();
            }
            if (string.IsNullOrEmpty(token))
            {
                Logger.LogInformation("無效的請求頭authorization,目標路徑{0}", Request.Path);
                return AuthenticateResult.NoResult();
            }

            var checkUser = Options.ValidateUser;
            if (checkUser == null)
            {
                Logger.LogInformation("Basic TokenValidator不能,目標路徑{0}", Request.Path);
                return await Task.FromResult(AuthenticateResult.NoResult());
            }

            try
            {
                var data = Encoding.UTF8.GetString(Convert.FromBase64String(token));
                if (string.IsNullOrEmpty(data)) throw new Exception("basic token 格式錯誤");

                string[] array = data.Split(KEY_SPLIT.ToCharArray());
                if (array.Length != 2) throw new Exception("basic token 格式錯誤");

                var username = array[0];
                var password = array[1];
                if (string.IsNullOrEmpty(username) || string.IsNullOrEmpty(password)) throw new Exception("basic token 格式錯誤");

                if (!checkUser(username, password))
                {
                    Logger.LogInformation("token 驗證失敗");
                    return AuthenticateResult.Fail("token 驗證失敗");
                }

                var claims = new List<Claim>()
                {
                    new Claim(ClaimTypes.Name, username)
                };

                var principer = new ClaimsPrincipal(new ClaimsIdentity(claims, BasicDefault.AuthenticationScheme));
                var validatedContext = new BasicTokenValidatedContext(Context, Scheme, Options)
                {
                    Principal = principer
                };

                await Events.TokenValidated(validatedContext);

                validatedContext.Success();

                return validatedContext.Result;
            }
            catch (Exception ex)
            {
                Logger.LogDebug(token + " validate failed: " + ex.Message);
                return AuthenticateResult.Fail(ex.Message);
            }

        }

        protected override async Task HandleChallengeAsync(AuthenticationProperties properties)
        {
            var authResult = await HandleAuthenticateOnceSafeAsync();

            Response.Headers.Add(HeaderNames.WWWAuthenticate, BasicDefault.AuthenticationScheme);
            Response.StatusCode = 401;
            if (authResult.Failure != null && !string.IsNullOrEmpty(authResult.Failure.Message))
            {
                var byteMsg = System.Text.Encoding.Default.GetBytes(authResult.Failure.Message);
                Response.Body.Write(byteMsg, 0, byteMsg.Length);
            }

            await base.HandleChallengeAsync(properties);
        }
    }

  BasicExtensions。用於提供注冊到.NET CORE的方法。

    public static class BasicExtensions
    {
        public static AuthenticationBuilder AddBasic(this AuthenticationBuilder builder)
            => builder.AddBasic(BasicDefault.AuthenticationScheme, _ => { });

        public static AuthenticationBuilder AddBasic(this AuthenticationBuilder builder, Action<BasicOption> configureOptions)
            => builder.AddBasic(BasicDefault.AuthenticationScheme, configureOptions);

        public static AuthenticationBuilder AddBasic(this AuthenticationBuilder builder, string authenticationScheme, Action<BasicOption> configureOptions)
            => builder.AddBasic(authenticationScheme, displayName: BasicDefault.DisplayName, configureOptions: configureOptions);

        public static AuthenticationBuilder AddBasic(this AuthenticationBuilder builder, string authenticationScheme, string displayName, Action<BasicOption> configureOptions)
        {
            return builder.AddScheme<BasicOption, BasicHandler>(authenticationScheme, displayName, configureOptions);
        }
    }

  以上就是完成Basic認證所有的方法。我們發現居然沒有涉及到 任何Middlerware的部分。實際原因是 官方實現了默認的 Authentication,里面有一個 IAuthenticationRequestHandler 的集合,我們創建的 AuthenticationHandler<T> 擴展就會加入該集合中,Authentication 會負責對集合中的 每一個 Handler 進行處理。源碼部分如下:

            var handlers = context.RequestServices.GetRequiredService<IAuthenticationHandlerProvider>();

            foreach (var scheme in await Schemes.GetRequestHandlerSchemesAsync())

            {

                var handler = await handlers.GetHandlerAsync(context, scheme.Name) as IAuthenticationRequestHandler;

                if (handler != null && await handler.HandleRequestAsync())

                {

                    return;

                }

            }

  最后我們將Basic認證注冊到.NET CORE中

            services.AddAuthentication(BasicDefault.AuthenticationScheme)
                .AddBasic(options =>
                {
                    options.ValidateUser = (username, password) =>
                    {
                        var clients = Configuration.GetSection("Clients").Get<List<ClientOptions>>();
                        if (clients == null || clients.Count == 0) return false;

                        return clients.Exists(x => string.Equals(x.Appkey, username, StringComparison.CurrentCultureIgnoreCase)
                                                                    && string.Equals(x.Appsecret, password, StringComparison.CurrentCultureIgnoreCase));
                    };
                    //options.Events = new AspNetCore.Authentication.Basic.Events.BasicEvents();
                    options.Events.OnTokenValidated = context =>
                    {
                        if (context.Principal.Identity.IsAuthenticated)
                        {
                            var clients = Configuration.GetSection("Clients").Get<List<ClientOptions>>();
                            if (clients == null || clients.Count == 0) return Task.CompletedTask;
                            var appkey = context.Principal.Identity.Name;
                            var actions = clients.Single(x => string.Equals(x.Appkey, appkey, StringComparison.CurrentCultureIgnoreCase)).Actions;
                            context.Properties.SetParameter("actions", actions);
                        }

                        return Task.CompletedTask;
                    };
                });

  別忘了 在 Configure 方法中加入 

app.UseAuthentication();

  好了,我們的Basic認證完成了~~

       后續問題,測試過程中發現 即使認證不通過的話 action 也能正常訪問,需要配合 Authorize 才能觸發 Challenge。這里我對Authentication和Authorization又增加了一點疑惑,按道理 前者負責確認 用戶,后者負責確認 用戶權限,但如果用戶確認為非法的情況下,為什么還要等到Authorization這塊來處理??

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM