升级Microsoft.Identity.Web.UI后Azure Web App的OpenIdConnect认证异常解决咨询
问题背景
我们有一个ASP.NET Core 3.1 Web应用,采用多种认证方案支持API和客户登录,原认证配置代码如下:
services.AddAuthentication(options => { options.DefaultAuthenticateScheme = "smart"; options.DefaultChallengeScheme = "smart"; }) .AddToken<TokenAuthenticationService>(options => { options.TokenFieldName = "Token"; }).AddPolicyScheme("smart", "Bearer or OpenIDConnect", options => { options.ForwardDefaultSelector = context => { string authHeader = context.Request.Headers["Authorization"]; if (authHeader?.StartsWith("Bearer ") == true) { return TokenAuthenticationDefaults.AuthenticationScheme; } return OpenIdConnectDefaults.AuthenticationScheme; }; });
应用使用Azure App Service经典版Easy Auth功能,升级Microsoft.Identity.Web.UI NuGet包从0.4.0-preview到2.15.3后,出现异常:No authentication handler is registered for the scheme 'OpenIdConnect.'。经排查,原因是Microsoft.Identity.Web 1.2.0+版本检测到Easy Auth时,会自动将默认认证方案替换为AppServicesAuthenticationDefaults,不再默认注册OpenIdConnect方案。
解决方法
1. 显式注册OpenIdConnect认证处理器
由于Microsoft.Identity.Web不再自动注册OpenIdConnect方案,需手动添加对应的认证处理器。在原认证配置链中补充注册逻辑:
services.AddAuthentication(options => { options.DefaultAuthenticateScheme = "smart"; options.DefaultChallengeScheme = "smart"; }) .AddToken<TokenAuthenticationService>(options => { options.TokenFieldName = "Token"; }) // 显式注册OpenIdConnect方案 .AddOpenIdConnect(OpenIdConnectDefaults.AuthenticationScheme, options => { // 从配置文件读取Azure AD相关参数 options.ClientId = Configuration["AzureAd:ClientId"]; options.Authority = Configuration["AzureAd:Authority"]; options.CallbackPath = Configuration["AzureAd:CallbackPath"]; // 根据需求添加其他配置(如签名验证、范围等) }) .AddPolicyScheme("smart", "Bearer or OpenIDConnect", options => { options.ForwardDefaultSelector = context => { string authHeader = context.Request.Headers["Authorization"]; if (authHeader?.StartsWith("Bearer ") == true) { return TokenAuthenticationDefaults.AuthenticationScheme; } return OpenIdConnectDefaults.AuthenticationScheme; }; });
如果使用Microsoft.Identity.Web的扩展方法,也可以用以下方式注册:
.AddMicrosoftIdentityWebApp(Configuration, OpenIdConnectDefaults.AuthenticationScheme)
2. 禁用Microsoft.Identity.Web的Easy Auth自动替换逻辑
在appsettings.json中添加配置项,阻止Microsoft.Identity.Web自动替换默认认证方案:
"AzureAd": { "DisableAutomaticAuthenticationSchemeSelection": true, // 保留原有的Azure AD配置参数 "ClientId": "your-client-id", "Authority": "your-authority", "CallbackPath": "/signin-oidc" }
此配置会让Microsoft.Identity.Web尊重你手动设置的默认认证方案(即smart PolicyScheme),不再自动切换为AppServicesAuthenticationDefaults。
3. 调整PolicyScheme转发逻辑兼容Easy Auth
如果需要保留Easy Auth的自动处理逻辑,可以修改PolicyScheme的转发规则,同时注册AppServicesAuthentication方案:
services.AddAuthentication(options => { options.DefaultAuthenticateScheme = "smart"; options.DefaultChallengeScheme = "smart"; }) .AddToken<TokenAuthenticationService>(options => { options.TokenFieldName = "Token"; }) // 注册AppServicesAuthentication方案以支持Easy Auth .AddAppServicesAuthentication() .AddPolicyScheme("smart", "Bearer or OpenIDConnect or Easy Auth", options => { options.ForwardDefaultSelector = context => { string authHeader = context.Request.Headers["Authorization"]; if (authHeader?.StartsWith("Bearer ") == true) { return TokenAuthenticationDefaults.AuthenticationScheme; } // 检测Easy Auth请求头,转发到对应的方案 if (!string.IsNullOrEmpty(context.Request.Headers["X-MS-CLIENT-PRINCIPAL-ID"])) { return AppServicesAuthenticationDefaults.AuthenticationScheme; } return OpenIdConnectDefaults.AuthenticationScheme; }; }) // 仍需显式注册OpenIdConnect方案 .AddOpenIdConnect(OpenIdConnectDefaults.AuthenticationScheme, options => { options.ClientId = Configuration["AzureAd:ClientId"]; options.Authority = Configuration["AzureAd:Authority"]; options.CallbackPath = Configuration["AzureAd:CallbackPath"]; });
复现问题的变通方法
本地模拟Easy Auth环境
- 添加模拟请求头:在本地调试时,通过Postman或浏览器插件手动添加Easy Auth相关请求头(如
X-MS-CLIENT-PRINCIPAL-ID、X-MS-CLIENT-PRINCIPAL-NAME),触发Microsoft.Identity.Web的Easy Auth检测逻辑。 - 设置环境变量:在本地运行环境中设置
WEBSITE_AUTH_ENABLED=True,模拟Azure App Service的认证启用状态,让Microsoft.Identity.Web自动触发方案替换逻辑。
使用临时Azure资源
如果允许创建临时资源,可以新建一个Azure App Service实例,启用Easy Auth(即使是新版,也能复现方案替换的核心逻辑),部署升级后的应用进行测试。
内容的提问来源于stack exchange,提问作者javacavaj

