• .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这块来处理??

  • 相关阅读:
    API网关服务
    技术攻关:从零到精通 https://mp.weixin.qq.com/s/mix-0Ft9G1F5yddNjSzkrw
    如何在团队中推广一项技术 —— 解决Odin一站式微前端的缓存问题
    设计模式的底层逻辑 找到变化,封装变化
    从Android内存到图片缓存优化
    百度C++工程师的那些极限优化(内存篇)
    享元模式
    协同编辑冲突处理算法综述
    大型前端项目内存优化总结
    雪碧图
  • 原文地址:https://www.cnblogs.com/fanfan-90/p/12462742.html
Copyright © 2020-2023  润新知