zoukankan      html  css  js  c++  java
  • .netcore 自定义多种身份验证方法混用

    背景:

    公司项目有很多租户,每个租户的系统都可能调用我们的租户服务,原来的解决方案是为每个租户提供一个service。随着租户的增多,service也多了起来,但是每个service里的逻辑都是一样的:验证身份,获取body,调用下游服务。

    重构:

    现在对外统一提供一个TenantService,里面只有一个Dispatcher方法。现在怎么知道进来的是哪个租户呢,这个租户要调用什么下游服务呢?这里我们用了一个最简单的方法,在Header添加了一个accesskey,我们为每个租户方法提供一个唯一的key,这个key在数据库中存放了对应的租户名,服务名,方法名,身份验证模式等。

    本文重点:

    本文主要针对不同的租户进来,可能采取不同的身份验证,比如,一个是Basic,另一个又是JWT等等。

    首先定义一个特性:CommonAuthenticationAttribute

      [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
        public class CommonAuthenticationAttribute : Attribute, IAuthorizationFilter
        {
            /// <summary>
            /// 1、从数据库中获取相应的租户名,服务名,方法名和身份验证模式
            /// 2、将租户名,服务名,方法名添加到请求的header中
            /// 3、调用相应的身份验证方法,失败则返回
            /// </summary>
            /// <param name="context"></param>
            public void OnAuthorization(AuthorizationFilterContext context)
            {
                var accessKey = GetHeaderValue(context, ConstantVar.AccessKey);
                //从数据库中获取相应的租户名,服务名,方法名和身份验证模式
                var routeData = GetRouteData(accessKey);
                //不合法的accesskey
                if (routeData == null)
                {
                    var errorMsg = $"Invalidate {ConstantVar.AccessKey} value";
                    context.Result = new ObjectResult(errorMsg) { StatusCode = 401 };
                }
                else
                {
                    string authType = routeData.authType;
                    if (!string.IsNullOrWhiteSpace(authType))
                    {
                        //重点:数据库中的authType的值一定要为已经实现的验证模式名字,
                        var res = context.HttpContext.AuthenticateAsync(authType).Result;
                        if (!res.Succeeded)//身份验证失败
                        {
                            context.Result = new ObjectResult(res.Failure.Message) { StatusCode = 401 };
                        }
                    }
                }
            }
        }

    以实现Basic验证为例,指定上面代码里的authType

      public class BasicAuthHandler : AuthenticationHandler<AuthenticationSchemeOptions>
        {
    
            public BasicAuthHandler(IOptionsMonitor<AuthenticationSchemeOptions> options,
                ILoggerFactory logger,
                UrlEncoder encoder,
                ISystemClock clock) : base(options, logger, encoder, clock)
            {
            }
    
            /// <summary>
            /// 验证用户名与密码
            /// </summary>
            /// <returns></returns>
            protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
            {
                // 跳过有匿名访问标签 [AllowAnonymous]
                var endpoint = Context.GetEndpoint();
                if (endpoint?.Metadata?.GetMetadata<IAllowAnonymous>() != null)
                    return AuthenticateResult.NoResult();
    
                if (!Request.Headers.ContainsKey("Authorization"))
                    return AuthenticateResult.Fail("Missing Authorization Header");
    
                try
                {
                    var authHeader = AuthenticationHeaderValue.Parse(Request.Headers["Authorization"]);
                    var credentialBytes = Convert.FromBase64String(authHeader.Parameter);
                    var credentials = Encoding.UTF8.GetString(credentialBytes).Split(new[] { ':' }, 2);
                    var username = credentials[0];
                    var password = credentials[1];
                    var res = await Authenticate(username, password);//验证用户名密码
                    if (res)
                    {
                        var claims = new[]
                        {
                            new Claim(ClaimTypes.Name, username),
                        };
                        var identity = new ClaimsIdentity(claims, Scheme.Name);
                        var principal = new ClaimsPrincipal(identity);
                        var ticket = new AuthenticationTicket(principal, Scheme.Name);
    
                        return AuthenticateResult.Success(ticket);
                    }
                    else
                    {
                        return AuthenticateResult.Fail("Unauthorized");
                    }
                }
                catch
                {
                    return AuthenticateResult.Fail("Invalid Authorization Header");
                }
    
            }
    
        }

    重点来了,在ConfigureServices里添加身份验证的模式。这里的“BasicAuth”就上面的authType

      services.AddAuthentication()
                    .AddScheme<AuthenticationSchemeOptions, BasicAuthHandler>("BasicAuth", null);//指定身份验证模式名,这里还可以添加多种验证模式

    至此,改造任务就基本完成了。然后只需要在Dispatcher方法上加上[CommonAuthentication]。

    以后其它租户进入,只需要提供给它一个唯一accesskey就可以了,如果有不同的身份验证方法,添加必要的验证模式就可以了。

  • 相关阅读:
    SpringBoot学习历程
    日期和时间库Joda Time
    apache commons validator
    apache commons fileupload
    apache commons io
    apache commons compress
    apache commons codec
    apache commons email
    Http协议介绍
    Java原生Socket API
  • 原文地址:https://www.cnblogs.com/ilyyin/p/13926803.html
Copyright © 2011-2022 走看看