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

如何让托管式Blazor WASM应用同时兼容Azure AD与自定义JWT登录?

为托管式Blazor WASM配置双登录系统(Azure AD + 自定义JWT)

要同时支持Azure AD内部用户和自定义JWT外部用户登录,核心是解决HttpClient冲突和认证状态管理两个问题,以下是具体实现步骤:

1. 分离两种认证对应的HttpClient配置

在客户端Program.cs中,为两种认证方式分别配置命名HttpClient,避免相互干扰:

using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using mipswebapp.Client;
using Blazored.Modal;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
using Blazored.SessionStorage;
using Blazored.LocalStorage;
using Microsoft.AspNetCore.Components.Authorization;

var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
builder.RootComponents.Add<HeadOutlet>("head::after");

builder.Services.AddBlazoredModal();
builder.Services.AddBlazoredSessionStorage();
builder.Services.AddBlazoredLocalStorage();

// 1. 配置Azure AD专用HttpClient,绑定MSAL授权消息处理器
builder.Services.AddHttpClient("AzureADApi", client => 
    client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress))
    .AddHttpMessageHandler<BaseAddressAuthorizationMessageHandler>();

// 2. 配置自定义JWT专用HttpClient,无MSAL处理器
builder.Services.AddHttpClient("CustomJwtApi", client => 
    client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress));

// 注册自定义JWT的认证状态Provider
builder.Services.AddScoped<ApiAuthenticationStateProvider>();
builder.Services.AddScoped<IAuthService, AuthService>();

// 配置MSAL认证(自动注册MsalAuthenticationStateProvider)
builder.Services.AddMsalAuthentication(options => {         
    builder.Configuration.Bind("AzureAd", options.ProviderOptions.Authentication);
    options.ProviderOptions.DefaultAccessTokenScopes.Add("placeholder_text"); 
    options.ProviderOptions.Cache.CacheLocation = "localStorage"; 
    options.ProviderOptions.LoginMode = "redirect";
});

// 注册复合认证状态Provider,整合两种认证的状态
builder.Services.AddScoped<AuthenticationStateProvider, CompositeAuthenticationStateProvider>();

await builder.Build().RunAsync();

2. 实现复合认证状态Provider

创建CompositeAuthenticationStateProvider类,同时监听Azure AD和自定义JWT的认证状态,返回当前有效的用户身份:

using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
using System.Security.Claims;

namespace mipswebapp.Client
{
    public class CompositeAuthenticationStateProvider : AuthenticationStateProvider
    {
        private readonly MsalAuthenticationStateProvider _msalProvider;
        private readonly ApiAuthenticationStateProvider _customJwtProvider;

        public CompositeAuthenticationStateProvider(
            MsalAuthenticationStateProvider msalProvider,
            ApiAuthenticationStateProvider customJwtProvider)
        {
            _msalProvider = msalProvider;
            _customJwtProvider = customJwtProvider;

            // 监听两个Provider的状态变化,同步更新当前状态
            _msalProvider.AuthenticationStateChanged += OnAuthStateChanged;
            _customJwtProvider.AuthenticationStateChanged += OnAuthStateChanged;
        }

        private void OnAuthStateChanged(Task<AuthenticationState> task)
        {
            NotifyAuthenticationStateChanged(GetAuthenticationStateAsync());
        }

        public override async Task<AuthenticationState> GetAuthenticationStateAsync()
        {
            // 优先检查Azure AD认证状态
            var msalState = await _msalProvider.GetAuthenticationStateAsync();
            if (msalState.User.Identity?.IsAuthenticated == true)
            {
                return msalState;
            }

            // 再检查自定义JWT认证状态
            var customState = await _customJwtProvider.GetAuthenticationStateAsync();
            if (customState.User.Identity?.IsAuthenticated == true)
            {
                return customState;
            }

            // 未认证时返回匿名身份
            return new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity()));
        }
    }
}

3. 组件中使用对应HttpClient

在需要调用API的组件中,通过IHttpClientFactory获取对应认证方式的HttpClient:

Azure AD认证场景

@inject IHttpClientFactory HttpClientFactory

<button @onclick="CallAzureAdApi">调用Azure AD保护的API</button>

@code {
    private async Task CallAzureAdApi()
    {
        var client = HttpClientFactory.CreateClient("AzureADApi");
        var response = await client.GetAsync("api/protected/azuread");
        response.EnsureSuccessStatusCode();
        var data = await response.Content.ReadAsStringAsync();
        // 处理返回数据
    }
}

自定义JWT认证场景

@inject IHttpClientFactory HttpClientFactory
@inject IAuthService AuthService

<button @onclick="CallCustomJwtApi">调用自定义JWT保护的API</button>

@code {
    private async Task CallCustomJwtApi()
    {
        var client = HttpClientFactory.CreateClient("CustomJwtApi");
        var token = await AuthService.GetTokenAsync();
        client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
        
        var response = await client.GetAsync("api/protected/customjwt");
        response.EnsureSuccessStatusCode();
        var data = await response.Content.ReadAsStringAsync();
        // 处理返回数据
    }
}

4. 后端API支持双认证

在ASP.NET Core后端中,同时配置Azure AD和自定义JWT的认证方案,并设置对应授权策略:

// 后端Program.cs
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.Identity.Web;

var builder = WebApplication.CreateBuilder(args);

// 添加双认证方案
builder.Services.AddAuthentication()
    // Azure AD认证
    .AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAd"))
    // 自定义JWT认证
    .AddJwtBearer(options =>
    {
        options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
        {
            ValidateIssuer = true,
            ValidateAudience = true,
            ValidateLifetime = true,
            ValidateIssuerSigningKey = true,
            ValidIssuer = builder.Configuration["Jwt:Issuer"],
            ValidAudience = builder.Configuration["Jwt:Audience"],
            IssuerSigningKey = new Microsoft.IdentityModel.Tokens.SymmetricSecurityKey(
                System.Text.Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"]))
        };
    });

// 配置授权策略
builder.Services.AddAuthorization(options =>
{
    // 仅允许Azure AD用户访问
    options.AddPolicy("AzureAdOnly", policy => 
        policy.RequireAuthenticatedUser()
              .AddAuthenticationSchemes(MicrosoftIdentityConstants.WebApiScheme));
    
    // 仅允许自定义JWT用户访问
    options.AddPolicy("CustomJwtOnly", policy => 
        policy.RequireAuthenticatedUser()
              .AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme));
    
    // 允许任意认证用户访问
    options.AddPolicy("AnyAuth", policy => 
        policy.RequireAuthenticatedUser()
              .AddAuthenticationSchemes(
                  MicrosoftIdentityConstants.WebApiScheme, 
                  JwtBearerDefaults.AuthenticationScheme));
});

builder.Services.AddControllers();

var app = builder.Build();

app.UseAuthentication();
app.UseAuthorization();

app.MapControllers();

app.Run();

然后在控制器上指定对应的授权策略:

// 仅Azure AD用户可访问
[Authorize(Policy = "AzureAdOnly")]
[ApiController]
[Route("api/protected/azuread")]
public class AzureAdProtectedController : ControllerBase
{
    [HttpGet]
    public IActionResult Get()
    {
        return Ok("Azure AD用户访问成功");
    }
}

// 仅自定义JWT用户可访问
[Authorize(Policy = "CustomJwtOnly")]
[ApiController]
[Route("api/protected/customjwt")]
public class CustomJwtProtectedController : ControllerBase
{
    [HttpGet]
    public IActionResult Get()
    {
        return Ok("自定义JWT用户访问成功");
    }
}

// 任意认证用户可访问
[Authorize(Policy = "AnyAuth")]
[ApiController]
[Route("api/protected/any")]
public class AnyAuthProtectedController : ControllerBase
{
    [HttpGet]
    public IActionResult Get()
    {
        return Ok("认证用户访问成功");
    }
}

内容的提问来源于stack exchange,提问作者Cole Slaw

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.21 11:37:08