Azure AD B2C:ASP.NET MVC应用内置与自定义用户流签名密钥适配问题
解决Azure AD B2C内置流与自定义流令牌验证兼容问题
我之前也碰到过这个问题,Azure AD B2C的内置用户流和自定义策略确实会使用不同的签名密钥——因为它们各自的OpenID元数据端点是独立的。只配置内置流的MetadataAddress的话,应用的令牌验证逻辑只会信任内置流的签名密钥,自然无法通过自定义流令牌的校验。下面给你两种可行的解决方案:
方案一:配置多认证方案分别处理两种流
这种方式最直接,给内置流和自定义流各自配置独立的OpenID Connect认证方案,让应用能识别并验证两种来源的令牌。
在Startup.cs的ConfigureServices方法里,添加两个AddOpenIdConnect配置:
services.AddAuthentication(options => { options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; }) .AddCookie() // 内置流认证方案 .AddOpenIdConnect("BuiltInFlow", options => { options.ClientId = "你的客户端ID"; options.MetadataAddress = "https://contoso.b2clogin.com/contoso.onmicrosoft.com/[built-in-flow]/v2.0/.well-known/openid-configuration"; options.ResponseType = OpenIdConnectResponseType.CodeIdToken; options.CallbackPath = "/signin-builtin"; options.SignedOutCallbackPath = "/signout-callback-builtin"; options.TokenValidationParameters = new TokenValidationParameters { NameClaimType = "name", RoleClaimType = "role" }; }) // 自定义流认证方案 .AddOpenIdConnect("CustomFlow", options => { options.ClientId = "你的客户端ID"; options.MetadataAddress = "https://contoso.b2clogin.com/contoso.onmicrosoft.com/[custom-policy]/v2.0/.well-known/openid-configuration"; options.ResponseType = OpenIdConnectResponseType.CodeIdToken; options.CallbackPath = "/signin-custom"; options.SignedOutCallbackPath = "/signout-callback-custom"; options.TokenValidationParameters = new TokenValidationParameters { NameClaimType = "name", RoleClaimType = "role" }; });
之后在控制器或者视图里,你可以通过[Authorize(AuthenticationSchemes = "BuiltInFlow,CustomFlow")]来允许两种方案的令牌通过验证,或者根据不同的场景指定对应的认证方案。
方案二:合并签名密钥,让单个认证方案信任两种流的密钥
如果你希望用同一个认证方案处理两种令牌,可以手动加载两个流的签名密钥,合并到令牌验证参数中。
步骤如下:
- 编写一个方法,从指定的Metadata地址获取签名密钥
- 在配置认证方案时,将两个流的密钥合并到
IssuerSigningKeys中
示例代码:
private async Task<IEnumerable<SecurityKey>> GetSigningKeysFromMetadata(string metadataUrl) { var httpClient = new HttpClient(); var metadataResponse = await httpClient.GetStringAsync(metadataUrl); var metadata = JsonDocument.Parse(metadataResponse).RootElement; var jwksUri = metadata.GetProperty("jwks_uri").GetString(); var jwksResponse = await httpClient.GetStringAsync(jwksUri); var jwks = JsonWebKeySet.Create(jwksResponse); return jwks.GetSigningKeys(); } // 在ConfigureServices中 services.AddAuthentication(options => { options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; }) .AddCookie() .AddOpenIdConnect("CombinedFlow", options => { options.ClientId = "你的客户端ID"; // 可以任选一个流的MetadataAddress作为基础 options.MetadataAddress = "https://contoso.b2clogin.com/contoso.onmicrosoft.com/[built-in-flow]/v2.0/.well-known/openid-configuration"; options.ResponseType = OpenIdConnectResponseType.CodeIdToken; options.CallbackPath = "/signin-oidc"; // 手动加载并合并两个流的签名密钥 var builtInKeys = await GetSigningKeysFromMetadata("https://contoso.b2clogin.com/contoso.onmicrosoft.com/[built-in-flow]/v2.0/.well-known/openid-configuration"); var customKeys = await GetSigningKeysFromMetadata("https://contoso.b2clogin.com/contoso.onmicrosoft.com/[custom-policy]/v2.0/.well-known/openid-configuration"); options.TokenValidationParameters = new TokenValidationParameters { NameClaimType = "name", RoleClaimType = "role", IssuerSigningKeys = builtInKeys.Concat(customKeys) }; });
额外说明
Azure AD B2C的内置流和自定义策略的签名密钥是由服务端管理的,无法强制让它们使用同一个密钥。所以核心思路就是让应用同时信任这两个流的签名密钥,上面两种方案都是围绕这个核心展开的。
内容的提问来源于stack exchange,提问作者Mark Prins
相关产品推荐
相关产品推荐

