Blazor Server应用如何兼容Microsoft Identity与自定义身份认证?
混合Azure AD与自定义用户名密码认证的解决方案
你的思路方向是对的,核心是通过装饰模式让自定义认证提供器回退到默认的Microsoft Identity认证逻辑,而非完全替换它。以下是具体实现步骤:
1. 改造自定义认证提供器
修改DealerAuthenticationStateProvider,注入默认的ServerAuthenticationStateProvider(Blazor Server中处理Cookie认证的原生提供器,对应Azure AD的登录状态),让它优先检查自定义外部用户的身份,无有效身份时回退到Azure AD认证。
public class DealerAuthenticationStateProvider : AuthenticationStateProvider { private const string LOCALSTORAGE_IDENTITY = "identity"; private const string LOCALSTORAGE_ACCESS_TOKEN = "access_token"; private readonly ProtectedLocalStorage _localStorage; private readonly ServerAuthenticationStateProvider _defaultAuthProvider; // 注入默认认证提供器 public DealerAuthenticationStateProvider( ProtectedLocalStorage localStorage, ServerAuthenticationStateProvider defaultAuthProvider) { _localStorage = localStorage; _defaultAuthProvider = defaultAuthProvider; } public override async Task<AuthenticationState> GetAuthenticationStateAsync() { // 1. 尝试获取自定义外部用户的身份 ProtectedBrowserStorageResult<string> storedToken = new(); try { storedToken = await _localStorage.GetAsync<string>(LOCALSTORAGE_IDENTITY); } catch(InvalidOperationException) {} ClaimsPrincipal customPrincipal = new ClaimsPrincipal(); bool hasValidCustomAuth = false; try { if(storedToken.Success && !string.IsNullOrEmpty(storedToken.Value)) { // 执行原有JWT解密、验证逻辑,生成自定义身份 customPrincipal = ...; // 你的原有代码 hasValidCustomAuth = customPrincipal.Identity?.IsAuthenticated ?? false; } } catch { // 令牌无效时清理存储 await _localStorage.DeleteAsync(LOCALSTORAGE_ACCESS_TOKEN); await _localStorage.DeleteAsync(LOCALSTORAGE_IDENTITY); } // 2. 自定义身份有效则返回,否则回退到Azure AD认证 if(hasValidCustomAuth) { return new AuthenticationState(customPrincipal); } else { return await _defaultAuthProvider.GetAuthenticationStateAsync(); } } // 原有SignInAsync逻辑保持不变 public async Task<bool> SignInAsync(DealerAuthFormModel form) { var principal = new ClaimsPrincipal(); if(...) // 验证经销商凭据逻辑 { var identity = new ClaimsIdentity(new Claim[] { new(ClaimTypes.Name, dealer.Name), new(ClaimTypes.NameIdentifier, dealer.AccountNumber) }, "DealerAuth"); // 自定义认证类型,与Azure AD区分 var token = ...; // 生成加密JWT await _localStorage.SetAsync(LOCALSTORAGE_IDENTITY, token); await _localStorage.SetAsync(LOCALSTORAGE_ACCESS_TOKEN, token); principal = new ClaimsPrincipal(identity); } NotifyAuthenticationStateChanged(Task.FromResult(new AuthenticationState(principal))); return principal.Identity != null; } // 原有SignOutAsync逻辑保持不变,仅清理自定义存储 public async Task SignOutAsync() { await _localStorage.DeleteAsync(LOCALSTORAGE_ACCESS_TOKEN); await _localStorage.DeleteAsync(LOCALSTORAGE_IDENTITY); NotifyAuthenticationStateChanged(Task.FromResult(new AuthenticationState(new ClaimsPrincipal()))); } }
2. 正确注册服务
调整服务注册逻辑,确保默认的ServerAuthenticationStateProvider被注入到自定义提供器中:
// 注册自定义认证提供器,注入依赖 builder.Services.AddScoped<DealerAuthenticationStateProvider>(sp => new DealerAuthenticationStateProvider( sp.GetRequiredService<ProtectedLocalStorage>(), sp.GetRequiredService<ServerAuthenticationStateProvider>() )); // 将自定义提供器设置为全局AuthenticationStateProvider builder.Services.AddScoped<AuthenticationStateProvider>(sp => sp.GetRequiredService<DealerAuthenticationStateProvider>()); // 保留原有Azure AD认证配置 builder.Services.AddAuthentication(...) .AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd"));
3. 登录流程区分
在UI层面提供两个登录入口,分别对应两种认证方式:
- 公司用户登录:使用Microsoft Identity原生的
<LoginDisplay>组件,触发Azure AD OAuth2登录流程,登录状态会存储在Cookie中,由ServerAuthenticationStateProvider读取。 - 外部用户登录:使用自定义表单,调用
DealerAuthenticationStateProvider.SignInAsync方法,将身份令牌存储在ProtectedLocalStorage中。
4. 权限与身份区分
可以通过User.Identity.AuthenticationType判断用户的登录方式,实现不同的权限控制或UI展示:
@if (context.User.Identity?.IsAuthenticated ?? false) { if(context.User.Identity.AuthenticationType == "DealerAuth") { <p>欢迎外部用户:@context.User.Identity.Name</p> <button @onclick="() => DealerAuthProvider.SignOutAsync()">退出登录</button> } else { <p>欢迎内部用户:@context.User.Identity.Name</p> <LoginDisplay /> } }
内容的提问来源于stack exchange,提问作者josh2112
相关产品推荐
相关产品推荐

