Asp.Net Core 中的多個 JWT 授權機構/發行人
我正在嘗試使用 Ocelot 在 ASP.Net API 網關中獲得 JWT 不記名身份驗證,以與多個授權機構/頒發者合作。一個發行者是Auth0,另一個是基於IdentityServer4的內部認證伺服器;我們正在嘗試從 Auth0 遷移,但仍有外部客戶端依賴它,因此我們希望同時支持這兩種客戶端,直到一切都經過全面測試以供切換。
根據this MSDN blog post,應該可以通過設置
TokenValidationParameters.ValidIssuers而不是使用多個權限JwtBearerOptions.Authority。但是,我已經在使用和不使用 Ocelot 的情況下對此進行了測試,如果沒有將授權設置為頒發令牌的授權,則不會發生身份驗證,無論TokenValidationParameters.ValidIssuers.有誰知道如何讓這個工作?這就是我設置身份驗證的方式。它僅在註釋行未註釋時才有效(並且僅適用於由該單一機構頒發的令牌)。我期待 Ocelot 或 ASP.Net Core 從發布伺服器獲取密鑰;兩者都通過 .well-known/openid-configuration 提供 JWK,它與 ASP.Net Core 中間件一起使用。
public static void AddJwtBearerAuthentication(this IServiceCollection services, IConfiguration configuration) { services .AddAuthentication(options => { options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; }) .AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options => { //options.Authority = configuration["Jwt:Authority"]; options.Audience = configuration["Jwt:Audience"]; options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = true, ValidateIssuerSigningKey = true, ValidateAudience = true, ValidAudience = configuration["Jwt:Audience"], ValidIssuers = configuration .GetSection("Jwt:Authorities") .AsEnumerable() .Select(kv => kv.Value) .Where(s => !string.IsNullOrEmpty(s)) .ToArray() }; }); }當有錯誤發行者的客戶端(或當我們使用
TokenValidationParameters.ValidIssuer/時ValidIssuers)連接時,Ocelot 的輸出是:[16:35:37 WRN] requestId: _____, previousRequestId: no previous request id, message: Error Code: UnauthenticatedError Message: Request for authenticated route _____ by was unauthenticated errors found in ResponderMiddleware. Setting error response for request path:_____, request method: POST這是一個 client_credentials 身份驗證,因此在“by”之後缺少使用者名。正如您所看到的,Ocelot 並沒有說明確切的問題是什麼。ASP.Net Core JWT 承載中間件(沒有 Ocelot)只是說簽名無效。我懷疑它要麼沒有看
TokenValidationParameters,要麼我誤解了它們的目的。
我想出瞭如何做到這一點:
- 使用
services.AddAuthentication(). 如果需要,您可以設置預設方案(為“Bearer”),但這不是必需的。- 添加任意數量的不同 JWT Bearer 配置
authenticationBuilder.AddJwtBearer(),每個配置都有自己的密鑰(例如“Auth0”、“IS4”、…)。我在 appsettings.json 中的數組上使用了循環- 創建一個策略方案
authenticationBuilder.AddPolicyScheme並為其指定方案名稱“Bearer”(用於JwtBearerDefaults.AuthenticationScheme避免在程式碼中包含魔術字元串)並options.ForwardDefaultSelector在回調中設置一個返回其他方案名稱之一的函式(“Auth0”、“IS4”或不管你放什麼)取決於一些標準。在我的情況下,它只是在 JWT 頒發者中查找方案名稱(如果頒發者包含“auth0”,則使用 Auth0 方案)。程式碼:
public static void AddMultiSchemeJwtBearerAuthentication( this IServiceCollection services, IConfiguration configuration ) { // Create JWT Bearer schemes. var schemes = configuration .GetSection("Jwt") .GetChildren() .Select(s => s.Key) .ToList() ; var authenticationBuilder = services.AddAuthentication(); foreach (var scheme in schemes) { authenticationBuilder.AddJwtBearer(scheme, options => { options.Audience = configuration[$"Jwt:{scheme}:Audience"]; options.Authority = configuration[$"Jwt:{scheme}:Authority"]; }); } // Add scheme selector. authenticationBuilder.AddPolicyScheme( JwtBearerDefaults.AuthenticationScheme, "Selector", options => { options.ForwardDefaultSelector = context => { // Find the first authentication header with a JWT Bearer token whose issuer // contains one of the scheme names and return the found scheme name. var authHeaderNames = new[] { HeaderNames.Authorization, HeaderNames.WWWAuthenticate }; StringValues headers; foreach (var headerName in authHeaderNames) { if (context.Request.Headers.TryGetValue(headerName, out headers) && !StringValues.IsNullOrEmpty(headers)) { break; } } if (StringValues.IsNullOrEmpty(headers)) { // Handle error. You can set context.Response.StatusCode and write a // response body. Returning null invokes default scheme which will raise // an exception; not sure how to fix this so the request is rejected. return null; } foreach (var header in headers) { var encodedToken = header.Substring(JwtBearerDefaults.AuthenticationScheme.Length + 1); var jwtHandler = new JwtSecurityTokenHandler(); var decodedToken = jwtHandler.ReadJwtToken(encodedToken); var issuer = decodedToken?.Issuer?.ToLower(); foreach (var scheme in schemes) { if (issuer?.Contains(scheme.ToLower()) == true) { // Found the scheme. return scheme; } } } // Handle error. return null; }; } ); }讓 Ocelot 支持這一點不需要什麼特別的,只需使用“Bearer”作為身份驗證提供程序密鑰,就會自動呼叫方案選擇器策略。
這是工作範例:
public void ConfigureServices(IServiceCollection services) { services.AddAuthentication(options => { options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; }) //set default authentication .AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options => { //set the next authentication configuration to be used options.ForwardDefaultSelector = ctx => "idp4"; //...rest of the options goes here }; }) .AddJwtBearer("idp4", options => { //set the next authentication configuration to be used options.ForwardDefaultSelector = ctx => "okta"; //options goes here }) .AddJwtBearer("okta", options => { //options goes here });