You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.22 19:23:08