.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就可以了,如果有不同的身份驗證方法,添加必要的驗證模式就可以了。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章