Blazor WebAssembly自定义JWT身份提供器下API控制器获取用户身份问题
Blazor WebAssembly调用API时无法传递JWT认证信息的解决方案
问题背景
我同时为Blazor Server和Blazor WebAssembly实现了自定义JWT AuthenticationStateProvider,在Blazor Server中可以正常通过AuthenticationState获取用户身份,但在Blazor WebAssembly中通过HttpClient调用服务器API时,控制器无法获取调用方的用户名。
相关代码
自定义JwtAuthenticationStateProvider
public class JwtAuthenticationStateProvider : AuthenticationStateProvider { private readonly ISessionStorageService sessionStorageService; private ClaimsPrincipal anonymous = new ClaimsPrincipal(new ClaimsIdentity()); public JwtAuthenticationStateProvider(ISessionStorageService sessionStorageService) { this.sessionStorageService = sessionStorageService; } public override async Task<AuthenticationState> GetAuthenticationStateAsync() { try { var userSession = await sessionStorageService.ReadEncryptedItemAsync<UserSession>("UserSession"); if (userSession == null) return await Task.FromResult(new AuthenticationState(anonymous)); var claimsPrincipal = new ClaimsPrincipal(new ClaimsIdentity(new List<Claim> { new Claim(ClaimTypes.Name, userSession.UserName), new Claim(ClaimTypes.Role, userSession.Role) }, "JwtAuth")); return await Task.FromResult(new AuthenticationState(claimsPrincipal)); } catch (Exception ex) { // 可在此处记录异常 return await Task.FromResult(new AuthenticationState(anonymous)); } } public async Task UpdateAuthenticationState(UserSession? userSession) { ClaimsPrincipal claimsPrincipal; if (userSession != null) { claimsPrincipal = new ClaimsPrincipal(new ClaimsIdentity(new List<Claim> { new Claim(ClaimTypes.Name, userSession.UserName), new Claim(ClaimTypes.Role, userSession.Role) })); userSession.ExpiryTimeStamp = DateTime.Now.AddSeconds(userSession.ExpiresIn); await sessionStorageService.SaveItemEncryptedAsync("UserSession", userSession); } else { claimsPrincipal = anonymous; await sessionStorageService.RemoveItemAsync("UserSession"); } NotifyAuthenticationStateChanged(Task.FromResult(new AuthenticationState(claimsPrincipal))); } public async Task<string> GetToken() { var result = string.Empty; try { var userSession = await sessionStorageService.ReadEncryptedItemAsync<UserSession>("UserSession"); if (userSession != null && DateTime.Now < userSession.ExpiryTimeStamp) result = userSession.Token; } catch (Exception ex) { // 可在此处记录异常 } return result; } }
服务器端API控制器
[Route("api/[controller]")] [ApiController] public class DevController : ControllerBase { [HttpGet, Route("GetUserNameByUser")] public string GetUserNameByUser() { return User?.Identity?.Name ?? String.Empty; // User?.Identity?.Name == null } [Microsoft.AspNetCore.Components.CascadingParameter] private Task<Microsoft.AspNetCore.Components.Authorization.AuthenticationState> AuthenticationState { get; set; } [HttpGet, Route("GetUserNameByAuthenticationState")] public async Task<ActionResult<string>> GetUserNameByAuthenticationState() { var currentUserName = (await AuthenticationState).User?.Identity?.Name; // AuthenticationState == null return Ok(currentUserName); } }
服务器端认证配置(Program.cs)
builder.Services.AddAuthentication(o => { o.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; o.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; }).AddJwtBearer(o => { o.RequireHttpsMetadata = true; o.SaveToken = true; o.TokenValidationParameters = new TokenValidationParameters { ValidateIssuerSigningKey = true, IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(JwtAuthenticationManager.JwtSecurityKey)), ValidateIssuer = false, ValidateAudience = false }; });
标准解决方案:自动为HttpClient添加JWT请求头
Blazor WebAssembly中需要配置HttpClient的消息处理程序,自动从自定义AuthenticationStateProvider获取JWT令牌并添加到请求头中,具体步骤如下:
1. 实现自定义AuthorizationMessageHandler
创建一个消息处理程序,拦截HttpClient请求并注入认证头:
public class JwtAuthorizationMessageHandler : DelegatingHandler { private readonly IServiceProvider _serviceProvider; public JwtAuthorizationMessageHandler(IServiceProvider serviceProvider) { _serviceProvider = serviceProvider; } protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { using var scope = _serviceProvider.CreateScope(); var authStateProvider = scope.ServiceProvider.GetRequiredService<JwtAuthenticationStateProvider>(); var token = await authStateProvider.GetToken(); if (!string.IsNullOrEmpty(token)) { request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token); } return await base.SendAsync(request, cancellationToken); } }
2. 在Blazor WebAssembly的Program.cs中注册HttpClient
注册带有自定义消息处理程序的HttpClient实例,供组件注入使用:
builder.Services.AddScoped<JwtAuthorizationMessageHandler>(); builder.Services.AddHttpClient("ServerAPI", client => client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress)) .AddHttpMessageHandler<JwtAuthorizationMessageHandler>(); // 注册供组件直接注入的HttpClient builder.Services.AddScoped(sp => sp.GetRequiredService<IHttpClientFactory>().CreateClient("ServerAPI"));
3. 确保服务器端启用认证中间件
在服务器端的Program.cs中,必须在路由之后添加认证和授权中间件:
app.UseRouting(); // 顺序不能错:先认证,再授权 app.UseAuthentication(); app.UseAuthorization(); app.MapControllers();
关键说明
- API控制器是HTTP请求驱动的,不能使用Blazor的
CascadingParameter获取AuthenticationState,必须通过请求头中的JWT令牌完成用户认证。 - 自定义消息处理程序会自动为指定地址的请求添加
Authorization: Bearer {token}头,服务器端的JWT认证中间件会验证令牌并自动填充User对象,此时User?.Identity?.Name就能正常获取到用户名。
内容的提问来源于stack exchange,提问作者Mario Duzioni
相关产品推荐
相关产品推荐

