Blazor Server调用已认证Web API:未授权处理及更优方案咨询
Blazor Server中OpenID认证后调用外部API的优化实现
一、自动注入AccessToken到HttpClient
手动给每个API请求加Authorization头太繁琐,推荐用自定义DelegatingHandler实现自动注入,一次配置全局生效:
1. 编写Token授权Handler
public class TokenAuthorizationHandler : DelegatingHandler { private readonly IHttpContextAccessor _httpContextAccessor; public TokenAuthorizationHandler(IHttpContextAccessor httpContextAccessor) { _httpContextAccessor = httpContextAccessor; } protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { // 从当前上下文获取登录后的Access Token var accessToken = await _httpContextAccessor.HttpContext.GetTokenAsync("access_token"); if (!string.IsNullOrEmpty(accessToken)) { request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); } return await base.SendAsync(request, cancellationToken); } }
2. 注册Handler和HttpClient
在Program.cs中注册必要服务,将Handler绑定到HttpClient:
// 注册HttpContext访问器(Blazor Server默认不提供) builder.Services.AddHttpContextAccessor(); // 注册自定义Handler builder.Services.AddScoped<TokenAuthorizationHandler>(); // 注册API服务,绑定Handler builder.Services.AddHttpClient<IMyApiService, MyApiService>() .AddHttpMessageHandler<TokenAuthorizationHandler>();
之后所有通过MyApiService发起的请求,都会自动带上Authorization头,不用再手动处理。
二、优雅处理未授权(401)响应
API返回401时,通常需要尝试刷新Token,失败则引导用户重新登录,推荐两种实现方式:
方式1:在Handler中直接处理401
修改上面的TokenAuthorizationHandler,加入401处理逻辑:
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { var accessToken = await _httpContextAccessor.HttpContext.GetTokenAsync("access_token"); if (!string.IsNullOrEmpty(accessToken)) { request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); } var response = await base.SendAsync(request, cancellationToken); // 处理未授权响应 if (response.StatusCode == HttpStatusCode.Unauthorized) { var refreshToken = await _httpContextAccessor.HttpContext.GetTokenAsync("refresh_token"); if (!string.IsNullOrEmpty(refreshToken)) { // 调用Identity Server的Token刷新接口 var tokenEndpoint = "https://your-identity-server/connect/token"; var tokenClient = new HttpClient(); var tokenResponse = await tokenClient.PostAsync(tokenEndpoint, new FormUrlEncodedContent(new Dictionary<string, string> { ["grant_type"] = "refresh_token", ["refresh_token"] = refreshToken, ["client_id"] = builder.Configuration["Auth:ClientId"], ["client_secret"] = builder.Configuration["Auth:ClientSecret"] }), cancellationToken); if (tokenResponse.IsSuccessStatusCode) { var newTokens = await tokenResponse.Content.ReadFromJsonAsync<TokenResponse>(); // 更新上下文的Token var authProperties = _httpContextAccessor.HttpContext.Properties; authProperties.UpdateTokenValue("access_token", newTokens.AccessToken); authProperties.UpdateTokenValue("refresh_token", newTokens.RefreshToken); // 重新发送请求 request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", newTokens.AccessToken); response = await base.SendAsync(request, cancellationToken); } else { // 刷新失败,登出并跳转登录页 await _httpContextAccessor.HttpContext.SignOutAsync(); var navigationManager = _httpContextAccessor.HttpContext.RequestServices.GetRequiredService<NavigationManager>(); navigationManager.NavigateTo("/login", forceLoad: true); } } else { // 无Refresh Token,直接登出 await _httpContextAccessor.HttpContext.SignOutAsync(); var navigationManager = _httpContextAccessor.HttpContext.RequestServices.GetRequiredService<NavigationManager>(); navigationManager.NavigateTo("/login", forceLoad: true); } } return response; }
方式2:用Polly实现重试+401处理
如果需要更灵活的重试策略,比如只重试一次,推荐用Polly库:
- 先安装Polly包:
Install-Package Polly - 在
Program.cs中配置HttpClient时添加Polly策略:
builder.Services.AddHttpClient<IMyApiService, MyApiService>() .AddHttpMessageHandler<TokenAuthorizationHandler>() .AddPolicyHandler(Policy.HandleResult<HttpResponseMessage>(r => r.StatusCode == HttpStatusCode.Unauthorized) .RetryAsync(1, async (result, retryCount, context) => { // 重试时执行Token刷新逻辑 var httpContextAccessor = context.GetService<IHttpContextAccessor>(); var refreshToken = await httpContextAccessor.HttpContext.GetTokenAsync("refresh_token"); if (!string.IsNullOrEmpty(refreshToken)) { // 同上面的刷新Token逻辑,获取新Token并更新上下文 // ... } }));
三、组件层简化错误处理
把API调用封装到服务类后,组件只需调用服务方法,统一捕获异常即可:
@inject IMyApiService ApiService @inject NavigationManager NavManager <button @onclick="LoadData">加载数据</button> @code { private async Task LoadData() { try { var data = await ApiService.GetData(); // 渲染数据 } catch (HttpRequestException ex) when (ex.StatusCode == HttpStatusCode.Unauthorized) { NavManager.NavigateTo("/login", forceLoad: true); } catch (Exception ex) { // 处理其他错误 } } }
注意事项
- 确保
IHttpContextAccessor已注册,Blazor Server默认不会自动提供。 - 刷新Token时,要和你的Identity Server配置匹配(比如是否需要ClientSecret、GrantType是否正确)。
- Blazor Server中跳转登录页需加
forceLoad: true,确保页面重新加载,清除旧的认证状态。
内容的提问来源于stack exchange,提问作者JamesL
相关产品推荐
相关产品推荐

