asp.net-core - 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”作为身份验证提供者密钥,就会自动调用方案选择器策略。
推荐阅读
- reactjs - 在 ASP.NET Core API 中处理 id_token
- spring - Spring Boot oauth:不支持的授权类型
- webview - Webview 不接受输入字段中的西里尔字符
- excel - 如何提取出现在句子中特定位置的单词?
- android - 设备离线时显示最新数据(android studio)
- ios - “try AVAudioSession.sharedInstance().setCategory”仅在设备上返回 nil
- security - 让用户永远登录到 React Native 应用程序
- javascript - 如何在 p5.js 中使用 vars 获取 json 数据
- csv - 如果使用 awk 或 sed 某些正则表达式为真,则更改列
- datastage - 领导时间戳的数据阶段?