如何让托管式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
相关产品推荐
相关产品推荐

