Blazor Server客户端JWT令牌通用处理方案求助
Blazor Server 通用JWT令牌处理方案修复指南
我已使用JWT令牌保护Web API,认证配置运行正常,但找不到Blazor Server客户端处理JWT令牌的相关指南。希望采用通用方式处理,避免在每个Razor组件中直接调用受保护API,因此编写了AuthClientService、TokenHandler并在Program.cs中配置,但方案无效,恳请提供可行的客户端JWT令牌通用处理方法。
我尝试的代码
AuthClientService.cs
public class AuthClientService { private readonly HttpClient httpClient; private readonly Blazored.LocalStorage.ILocalStorageService localStorage; private string token; private DateTime tokenExpiration; private const string TokenKey = "auth_token"; public AuthClientService(HttpClient httpClient, Blazored.LocalStorage.ILocalStorageService localStorage) { this.httpClient = httpClient; this.localStorage = localStorage; } public async Task<string> GetTokenAsync() { var token = await localStorage.GetItemAsStringAsync(TokenKey); // Check if the token is null or expired if (string.IsNullOrEmpty(token))//|| IsTokenExpired(token)) { // Request a new token token = await RequestNewToken(); // Save the token to local storage await localStorage.SetItemAsync(TokenKey, token); } return token; } private async Task<string> RequestNewToken() { var user = new User("hams@email.com", "hams"); var response = await httpClient.PostAsJsonAsync("api/auth", user); if (response.IsSuccessStatusCode) { var token = await response.Content.ReadAsStringAsync(); var jwtToken = token.Trim('"'); // Parse the token and extract the expiration date var tokenHandler = new JwtSecurityTokenHandler(); var parsedToken = tokenHandler.ReadJwtToken(jwtToken); tokenExpiration = parsedToken.ValidTo; return await response.Content.ReadAsStringAsync(); } else { return null; } } }
TokenHandler.cs
public class TokenHandler : DelegatingHandler { private readonly AuthClientService authService; public TokenHandler(AuthClientService authService) { this.authService = authService; } protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { var token = await authService.GetTokenAsync(); if (!string.IsNullOrEmpty(token)) { request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token); } return await base.SendAsync(request, cancellationToken); } }
Program.cs
builder.Services.AddBlazoredLocalStorage(); builder.Services.AddScoped(async sp => { var httpClient = new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }; // Add the TokenHandler to the HttpClient's pipeline var authClientService = sp.GetRequiredService<AuthClientService>(); var tokenHandler = new TokenHandler(authClientService); httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", await authClientService.GetTokenAsync()); return httpClient; });
问题分析与修复方案
你的方案无效核心原因有三点:
- HttpClient未正确集成TokenHandler:未将自定义处理器加入请求管道,导致令牌无法自动附加
- 依赖循环问题:
AuthClientService依赖HttpClient,注册HttpClient时又依赖AuthClientService,导致注入失败 - 令牌过期检查缺失:注释了过期校验逻辑,无法自动刷新失效令牌
修复后的完整实现
1. 完善AuthClientService(添加缓存与过期校验)
public class AuthClientService { private readonly HttpClient _httpClient; private readonly Blazored.LocalStorage.ILocalStorageService _localStorage; private string _cachedToken; private DateTime _tokenExpiration; private const string TokenKey = "auth_token"; public AuthClientService(HttpClient httpClient, Blazored.LocalStorage.ILocalStorageService localStorage) { _httpClient = httpClient; _localStorage = localStorage; } public async Task<string> GetValidTokenAsync() { // 优先使用缓存的有效令牌(提前1分钟刷新避免边缘过期) if (!string.IsNullOrEmpty(_cachedToken) && DateTime.UtcNow < _tokenExpiration.AddMinutes(-1)) { return _cachedToken; } // 从本地存储读取令牌并校验有效期 var storedToken = await _localStorage.GetItemAsStringAsync(TokenKey); if (!string.IsNullOrEmpty(storedToken)) { var pureToken = storedToken.Trim('"'); var parsedToken = new JwtSecurityTokenHandler().ReadJwtToken(pureToken); _tokenExpiration = parsedToken.ValidTo; if (DateTime.UtcNow < _tokenExpiration.AddMinutes(-1)) { _cachedToken = pureToken; return _cachedToken; } } // 请求新令牌并更新缓存与本地存储 var newToken = await RequestNewToken(); if (!string.IsNullOrEmpty(newToken)) { _cachedToken = newToken; await _localStorage.SetItemAsync(TokenKey, $"\"{newToken}\""); return newToken; } throw new UnauthorizedAccessException("无法获取有效认证令牌"); } private async Task<string> RequestNewToken() { var user = new User("hams@email.com", "hams"); var response = await _httpClient.PostAsJsonAsync("api/auth", user); if (response.IsSuccessStatusCode) { var tokenString = await response.Content.ReadAsStringAsync(); var pureToken = tokenString.Trim('"'); var parsedToken = new JwtSecurityTokenHandler().ReadJwtToken(pureToken); _tokenExpiration = parsedToken.ValidTo; return pureToken; } return null; } }
2. 调整TokenHandler(解决依赖循环)
通过IServiceProvider延迟解析服务,避免循环依赖,同时添加401重试逻辑:
public class TokenHandler : DelegatingHandler { private readonly IServiceProvider _serviceProvider; public TokenHandler(IServiceProvider serviceProvider) { _serviceProvider = serviceProvider; } protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { using var scope = _serviceProvider.CreateScope(); var authService = scope.ServiceProvider.GetRequiredService<AuthClientService>(); var token = await authService.GetValidTokenAsync(); if (!string.IsNullOrEmpty(token)) { request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token); } var response = await base.SendAsync(request, cancellationToken); // 令牌过期返回401时,刷新令牌并重试一次 if (response.StatusCode == HttpStatusCode.Unauthorized) { var newToken = await authService.GetValidTokenAsync(); if (!string.IsNullOrEmpty(newToken)) { request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", newToken); response = await base.SendAsync(request, cancellationToken); } } return response; } }
3. 正确注册服务到Program.cs
builder.Services.AddBlazoredLocalStorage(); // 先注册AuthClientService builder.Services.AddScoped<AuthClientService>(); // 注册TokenHandler builder.Services.AddTransient<TokenHandler>(); // 配置HttpClient并集成TokenHandler builder.Services.AddScoped(sp => { var tokenHandler = sp.GetRequiredService<TokenHandler>(); tokenHandler.InnerHandler = new HttpClientHandler(); var httpClient = new HttpClient(tokenHandler) { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }; return httpClient; });
使用方式
在Razor组件或服务中直接注入HttpClient即可,所有请求会自动附加JWT令牌:
@inject HttpClient Http @code { protected override async Task OnInitializedAsync() { var data = await Http.GetFromJsonAsync<MyData>("api/protected/data"); // 处理返回数据 } }
内容的提问来源于stack exchange,提问作者Sapri s
相关产品推荐
相关产品推荐

