.netcore 自定義多種身份驗證方法混用
- 2020 年 11 月 4 日
- 筆記
背景:
公司項目有很多租戶,每個租戶的系統都可能調用我們的租戶服務,原來的解決方案是為每個租戶提供一個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就可以了,如果有不同的身份驗證方法,添加必要的驗證模式就可以了。