Blazor WASM应用同时实现JWT与Azure AD认证的冲突解决问询
问题根源
AddMsalAuthentication方法执行时会自动注册MSAL官方的AuthenticationStateProvider实现,和你自定义的JWT认证状态提供器存在全局注册冲突,二者只会有一个生效,导致另一种登录方式的身份状态无法被正常读取。
解决配置步骤
你可以通过实现复合认证状态提供器兼容两种登录方式,无需修改原有单独运行时的业务逻辑:
1. 保留现有MSAL注册代码
你贴出的Azure AD配置代码无需调整,保持原有配置即可:
builder.Services.AddMsalAuthentication<RemoteAuthenticationState, CustomUserAccount>(options => { builder.Configuration.Bind("AzureAd", options.ProviderOptions.Authentication); options.ProviderOptions.DefaultAccessTokenScopes.Add("api://2913a4cb-b93e-4107-bd31-1f963e967f30/API.Access"); options.UserOptions.RoleClaim = "roles"; options.ProviderOptions.Cache.CacheLocation = "localStorage"; options.ProviderOptions.LoginMode = "redirect"; }).AddAccountClaimsPrincipalFactory<RemoteAuthenticationState, CustomUserAccount, CustomAccountFactory>();
2. 实现复合认证状态提供器
新建HybridAuthenticationStateProvider,同时对接两种认证逻辑:
public class HybridAuthenticationStateProvider : AuthenticationStateProvider { // 注入MSAL官方认证提供器 private readonly MsalAuthenticationStateProvider _msalProvider; // 注入你原有自定义JWT认证提供器 private readonly CustomJwtAuthenticationStateProvider _customJwtProvider; public HybridAuthenticationStateProvider(MsalAuthenticationStateProvider msalProvider, CustomJwtAuthenticationStateProvider customJwtProvider) { _msalProvider = msalProvider; _customJwtProvider = customJwtProvider; // 同步两个提供器的状态变更事件 _msalProvider.AuthenticationStateChanged += state => NotifyAuthenticationStateChanged(state); _customJwtProvider.AuthenticationStateChanged += state => NotifyAuthenticationStateChanged(state); } public override async Task<AuthenticationState> GetAuthenticationStateAsync() { // 优先检查Azure AD登录态 var msalState = await _msalProvider.GetAuthenticationStateAsync(); if (msalState.User.Identity?.IsAuthenticated == true) { return msalState; } // 无Azure AD登录态则检查自定义JWT登录态 return await _customJwtProvider.GetAuthenticationStateAsync(); } }
3. 调整服务注册顺序
- 单独注册两个子认证提供器,不要直接覆盖全局
AuthenticationStateProvider - 最后注册复合提供器作为全局生效的认证状态提供器
// 单独注册自定义JWT提供器本身 builder.Services.AddScoped<CustomJwtAuthenticationStateProvider>(); // 这里放步骤1的MSAL注册代码 // ... // 注册复合提供器作为全局认证状态提供器 builder.Services.AddScoped<AuthenticationStateProvider, HybridAuthenticationStateProvider>();
4. 适配API调用的授权逻辑
自定义DelegatingHandler,根据登录来源自动附加对应token到请求头:
public class HybridAuthorizationMessageHandler : DelegatingHandler { private readonly IAccessTokenProvider _msalTokenProvider; private readonly CustomJwtStore _customJwtStore; // 你自己实现的JWT存储服务 private readonly AuthenticationStateProvider _authStateProvider; public HybridAuthorizationMessageHandler(IAccessTokenProvider msalTokenProvider, CustomJwtStore customJwtStore, AuthenticationStateProvider authStateProvider) { _msalTokenProvider = msalTokenProvider; _customJwtStore = customJwtStore; _authStateProvider = authStateProvider; } protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { var authState = await _authStateProvider.GetAuthenticationStateAsync(); if (!authState.User.Identity.IsAuthenticated) { return await base.SendAsync(request, cancellationToken); } // 提前在两种登录逻辑里给用户增加auth_type标识Claim,Azure AD加auth_type=azuread,自定义登录加auth_type=custom var authType = authState.User.FindFirst("auth_type")?.Value; if (authType == "azuread") { var tokenResult = await _msalTokenProvider.RequestAccessToken(); if (tokenResult.TryGetToken(out var token)) { request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token.Value); } } else if (authType == "custom") { var jwt = await _customJwtStore.GetTokenAsync(); if (!string.IsNullOrEmpty(jwt)) { request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", jwt); } } return await base.SendAsync(request, cancellationToken); } }
注册HTTP客户端时使用该Handler:
builder.Services.AddScoped<HybridAuthorizationMessageHandler>(); builder.Services.AddHttpClient("YourApiClient", client => { client.BaseAddress = new Uri(builder.Configuration["ApiBaseUrl"]); }).AddHttpMessageHandler<HybridAuthorizationMessageHandler>();
配套注意事项
- 两种登录方式的公共Claim(角色、用户ID、用户名等)要统一命名,避免页面授权校验逻辑出错
- 退出登录时根据当前用户的
auth_type调用对应退出逻辑:Azure AD调用MSAL的登出方法,自定义登录清除本地存储的JWT即可 - 两种登录方式的有效期校验逻辑保持各自原有实现即可,复合提供器会自动读取生效状态
内容的提问来源于stack exchange,提问作者Martin Dempsey
相关产品推荐
相关产品推荐

