Asp.Net 核心中的多个 JWT authorities/issuers
Multiple JWT authorities/issuers in Asp.Net Core
我正在尝试在 ASP.Net API 网关中使用 Ocelot 与多个 authorities/issuers 一起工作来获取 JWT 承载身份验证。一个issuer是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 核心中间件一起工作。
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 核心 JWT 承载中间件(没有 Ocelot)只是说签名无效。我怀疑它没有在查看 TokenValidationParameters
,或者我误解了它们的用途。
我知道怎么做了:
使用 services.AddAuthentication()
创建身份验证生成器。如果需要,您可以设置默认方案(“Bearer”),但这不是必需的。
使用 authenticationBuilder.AddJwtBearer()
添加任意数量的不同 JWT Bearer 配置,每个配置都有自己的密钥(例如“Auth0”、“IS4”、...)。我在 appsettings.json
中对数组使用了循环
使用 authenticationBuilder.AddPolicyScheme
创建策略方案并为其指定方案名称“Bearer”(使用 JwtBearerDefaults.AuthenticationScheme
以避免代码中出现魔术字符串)并设置 options.ForwardDefaultSelector
在函数的回调中 returns 其他方案名称之一(“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
});
.net 5 的工作解决方案。
这将适用于多个 JWT 不记名令牌发行者
默认模式将路由到相应的模式
// Get list of domains and audience from the config
var authorities = Configuration["Auth:Domain"].Split(',').Distinct().ToList();
var audience = Configuration["Auth:Audience"];
// Add default empty schema schema selection policy
var authenticationBuilder = services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(
options =>
{
// forward to corresponding schema based on token's issuer
// this will read the token and check the token issues , if the token issuer is registered in config then redirect to that schema
options.ForwardDefaultSelector = context =>
{
string authorization = context.Request.Headers[HeaderNames.Authorization];
if (!string.IsNullOrEmpty(authorization))
{
if (authorization.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase))
{
var token = authorization.Substring("Bearer ".Length).Trim();
var jwtHandler = new JwtSecurityTokenHandler();
if (jwtHandler.CanReadToken(token))
{
var jwtToken = jwtHandler.ReadJwtToken(token);
if (authorities.Contains(jwtToken.Issuer))
return jwtToken.Issuer;
}
}
}
return null;
};
});
// Register all configured schemas
foreach (var auth in authorities)
{
authenticationBuilder.AddJwtBearer(auth, options =>
{
options.SaveToken = true;
options.Audience = audience;
options.Authority = auth;
options.TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = "sub",
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
RequireSignedTokens = true,
ValidateIssuerSigningKey = true
};
});
}
我正在尝试在 ASP.Net API 网关中使用 Ocelot 与多个 authorities/issuers 一起工作来获取 JWT 承载身份验证。一个issuer是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 核心中间件一起工作。
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 核心 JWT 承载中间件(没有 Ocelot)只是说签名无效。我怀疑它没有在查看 TokenValidationParameters
,或者我误解了它们的用途。
我知道怎么做了:
使用
services.AddAuthentication()
创建身份验证生成器。如果需要,您可以设置默认方案(“Bearer”),但这不是必需的。使用
中对数组使用了循环authenticationBuilder.AddJwtBearer()
添加任意数量的不同 JWT Bearer 配置,每个配置都有自己的密钥(例如“Auth0”、“IS4”、...)。我在 appsettings.json使用
authenticationBuilder.AddPolicyScheme
创建策略方案并为其指定方案名称“Bearer”(使用JwtBearerDefaults.AuthenticationScheme
以避免代码中出现魔术字符串)并设置options.ForwardDefaultSelector
在函数的回调中 returns 其他方案名称之一(“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
});
.net 5 的工作解决方案。
这将适用于多个 JWT 不记名令牌发行者
默认模式将路由到相应的模式
// Get list of domains and audience from the config var authorities = Configuration["Auth:Domain"].Split(',').Distinct().ToList(); var audience = Configuration["Auth:Audience"]; // Add default empty schema schema selection policy var authenticationBuilder = services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer( options => { // forward to corresponding schema based on token's issuer // this will read the token and check the token issues , if the token issuer is registered in config then redirect to that schema options.ForwardDefaultSelector = context => { string authorization = context.Request.Headers[HeaderNames.Authorization]; if (!string.IsNullOrEmpty(authorization)) { if (authorization.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase)) { var token = authorization.Substring("Bearer ".Length).Trim(); var jwtHandler = new JwtSecurityTokenHandler(); if (jwtHandler.CanReadToken(token)) { var jwtToken = jwtHandler.ReadJwtToken(token); if (authorities.Contains(jwtToken.Issuer)) return jwtToken.Issuer; } } } return null; }; }); // Register all configured schemas foreach (var auth in authorities) { authenticationBuilder.AddJwtBearer(auth, options => { options.SaveToken = true; options.Audience = audience; options.Authority = auth; options.TokenValidationParameters = new TokenValidationParameters { NameClaimType = "sub", ValidateIssuer = true, ValidateAudience = true, ValidateLifetime = true, RequireSignedTokens = true, ValidateIssuerSigningKey = true }; }); }