如何在同一.NET Core API中配置Azure AD与非Azure AD双令牌鉴权
.NET Core 多鉴权方案共存实现
原代码启动崩溃的核心原因
代码崩溃本质是鉴权方案名重复注册:AddMicrosoftIdentityWebApi扩展方法默认会使用JwtBearerDefaults.AuthenticationScheme(即常量值"Bearer")作为它的注册方案名,而你此前已经用完全相同的方案名注册了自定义JWT鉴权。ASP.NET Core鉴权框架不允许同名方案重复注册,这种情况会直接在应用启动阶段抛出InvalidOperationException,根本到不了运行阶段。
你之前遇到的“给两个方案分别设置独立名称后[Authorize]不生效”,本质是默认鉴权策略配置不当,不是独立方案命名方式的问题。
正确的服务配置实现
1. 定义全局统一的方案名称常量
避免后续写代码时硬编码字符串写错,造成方案匹配失效:
public static class CustomAuthenticationSchemes { /// <summary>自定义非Azure AD的JWT鉴权方案</summary> public const string CustomJwt = "CustomJwt"; /// <summary>Azure AD托管的JWT鉴权方案</summary> public const string AzureAdJwt = "AzureAdJwt"; /// <summary>多方案路由的中间层策略名</summary> private const string MultiSchemeRouter = "MultiAuthRouter"; }
2. 注册双鉴权方案,避免命名冲突
这里用框架自带的AddPolicyScheme做请求级别的方案路由,解决两个JWT方案共存时的默认匹配问题,彻底解决“[Authorize]无参数时不生效”的问题:
services.AddAuthentication(options => { // 将默认鉴权入口指向路由层,而不是某一个具体的JWT方案 options.DefaultScheme = CustomAuthenticationSchemes.MultiSchemeRouter; options.DefaultChallengeScheme = CustomAuthenticationSchemes.MultiSchemeRouter; }) // 配置路由层逻辑:根据请求特征自动匹配需要使用的鉴权方案 .AddPolicyScheme(CustomAuthenticationSchemes.MultiSchemeRouter, "多鉴权方案路由", options => { options.OnSelectedScheme = context => { // 路由规则可以根据实际需求灵活定制: // 方式1:根据自定义请求头判断来源 var authSource = context.Request.Headers["X-Auth-Source"].FirstOrDefault(); // 方式2:也可以直接解析Authorization头里的令牌前缀,不依赖额外请求头 // var authHeader = context.Request.Headers["Authorization"].FirstOrDefault(); // var token = authHeader?.Replace("Bearer ", ""); return authSource switch { "Custom" => Task.FromResult(CustomAuthenticationSchemes.CustomJwt), "AzureAd" => Task.FromResult(CustomAuthenticationSchemes.AzureAdJwt), // 兜底规则:未标识来源时默认走Azure AD鉴权 _ => Task.FromResult(CustomAuthenticationSchemes.AzureAdJwt) }; }; }) // 注册自定义JWT方案,显式指定独立方案名 .AddJwtBearer(CustomAuthenticationSchemes.CustomJwt, options => { options.TokenValidationParameters = new TokenValidationParameters() { ValidateAudience = false, ValidateIssuer = false, ValidateIssuerSigningKey = false, ValidateLifetime = true, IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("private_key")) }; }) // 注册Azure AD鉴权,显式传入自定义方案名,覆盖默认的"Bearer"命名 .AddMicrosoftIdentityWebApi( configuration: this.Configuration.GetSection("AzureAd"), bearerScheme: CustomAuthenticationSchemes.AzureAdJwt );
3. [Authorize]特性的灵活使用
配置完成后不需要修改框架的其他逻辑,直接按场景使用[Authorize]即可:
- 控制器/接口需要兼容两种令牌都能访问:直接写无参数的
[Authorize],框架会通过之前配置的路由层自动匹配对应鉴权逻辑,不会出现之前的401拦截问题。 - 控制器/接口需要限制只允许某一类令牌访问:在[Authorize]中显式指定对应方案名即可:
- 仅允许自定义JWT访问:
[Authorize(AuthenticationSchemes = CustomAuthenticationSchemes.CustomJwt)] - 仅允许Azure AD令牌访问:
[Authorize(AuthenticationSchemes = CustomAuthenticationSchemes.AzureAdJwt)]
- 仅允许自定义JWT访问:
实操中的常见注意事项
- 永远不要给两个不同的鉴权逻辑设置相同的方案名:只要同名方案重复注册,应用启动时就会直接抛出异常,不存在运行时再判断的可能。
- 不要把默认鉴权方案直接绑定到某一个具体的JWT实现:如果把DefaultScheme直接设为CustomJwt,所有不带参数的[Authorize]请求都会走自定义JWT校验,携带Azure AD令牌的请求会被直接拒绝,这就是之前遇到“[Authorize]不生效”的根本原因。
- 生产环境不要全量关闭JWT校验规则:代码里将Audience、Issuer、SigningKey的校验全部关闭,存在极高的令牌伪造风险,哪怕是自定义JWT,也至少需要校验签名密钥和令牌发行人,不能完全放开校验逻辑。
- 路由规则可以不依赖额外请求头:如果不想让前端额外传
X-Auth-Source请求头,可以在路由层直接解析令牌的Payload段,根据自定义JWT和Azure AD令牌的固有声明差异自动匹配对应方案,用户侧完全无感知。
内容的提问来源于stack exchange,提问作者user2608376
相关产品推荐
相关产品推荐

