在 Blazor 应用中,前端经常需要访问带有身份校验的 Web API。服务端通常采用 JWT(JSON Web Token)来识别用户,这就要求每次 HTTP 请求都在头部带上 Authorization 字段。Blazor 的 HttpClient 默认不会自动附加令牌,开发者必须显式处理,否则接口会返回 401。

为什么不能直接在组件里写 Token
很多初学者会在页面代码的 OnInitialized 或按钮事件里,用 HttpClient.DefaultRequestHeaders.Add 手动塞入 Token。这种做法在小型 demo 里看似可行,但实际项目会带来明显问题。首先,Token 往往存放在本地存储或认证状态里,每个需要调用的组件都要重复读取和赋值,代码冗余且难以维护。
其次,JWT 具有有效期,过期后需要刷新。如果 Token 散落在几十个组件内,刷新逻辑也得写几十遍,极易出现某些请求用了旧令牌而报错的情况。最后,手动添加容易因为大小写、格式错误导致服务端解析失败。因此更推荐通过统一的消息处理管道来自动附加。
使用 DelegatingHandler 统一附加 Token
Blazor 的 HttpClient 基于 .NET 的 HttpMessageHandler 管道,我们可以自定义一个继承自 DelegatingHandler 的类,在 SendAsync 方法中拦截请求并添加头部。这样所有经过该 HttpClient 的请求都会自动带上 JWT,组件层完全无感知。
下面代码展示了一个简单的令牌处理程序,它从注入的 AuthenticationStateProvider 获取当前用户,并读取 Claim 中的 token 声明。如果取到值,就写入 Authorization 头,格式为 Bearer 加空格加令牌内容。
using System.Net.Http.Headers;
using Microsoft.AspNetCore.Components.Authorization;
using System.Security.Claims;
public class JwtTokenHandler : DelegatingHandler
{
private readonly AuthenticationStateProvider _authProvider;
public JwtTokenHandler(AuthenticationStateProvider authProvider)
{
_authProvider = authProvider;
}
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
// 获取当前认证状态
var authState = await _authProvider.GetAuthenticationStateAsync();
var user = authState.User;
// 从 Claim 中读取 token,注意 Claim 类型需与服务端一致
var tokenClaim = user.FindFirst("access_token");
if (tokenClaim != null && !string.IsNullOrEmpty(tokenClaim.Value))
{
request.Headers.Authorization =
new AuthenticationHeaderValue("Bearer", tokenClaim.Value);
}
// 调用下一个处理程序或实际发送请求
return await base.SendAsync(request, cancellationToken);
}
}
在 Program 中注册处理程序
定义好处理器后,需要在应用入口将其挂到 HttpClient 上。Blazor WebAssembly 或 Server 都可以在 Program.cs 里用 AddHttpMessageHandler 扩展方法完成绑定。同时要注意 HttpClient 的 BaseAddress 必须设置,否则请求会抛异常。
以下示例演示如何注册一个具名 HttpClient,并把 JwtTokenHandler 插入消息管道。通过作用域方式注入 AuthenticationStateProvider,可以保证处理器能拿到最新的登录状态。
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using Microsoft.Extensions.DependencyInjection;
using System.Net.Http;
var builder = WebAssemblyHostBuilder.CreateDefault(args);
// 注册认证服务(假设已配置好)
builder.Services.AddAuthorizationCore();
builder.Services.AddCascadingAuthenticationState();
// 注册自定义 Token 处理程序
builder.Services.AddScoped<JwtTokenHandler>();
// 配置带 Token 的 HttpClient
builder.Services.AddHttpClient("ApiClient", client =>
{
client.BaseAddress = new Uri("https://ipipp.com/api/");
}).AddHttpMessageHandler<JwtTokenHandler>();
// 也可以提供一个方便注入的强类型客户端
builder.Services.AddScoped(sp =>
sp.GetRequiredService<IHttpClientFactory>()
.CreateClient("ApiClient"));
await builder.Build().RunAsync();
在组件中调用带 Token 的客户端
注册完成后,组件只需通过 IHttpClientFactory 或直接注入命名客户端来发起请求,不再关心 Token 细节。下面例子展示在 Razor 组件里获取天气数据,由于管道已自动附加 JWT,代码非常干净。
如果服务端返回 401,说明 Token 失效或 Claim 名称不匹配,此时应引导用户重新登录,而不是在业务代码里反复判断头部。集中式的处理器让异常拦截和全局刷新令牌都更容易实现。
@inject IHttpClientFactory HttpClientFactory
@code {
private WeatherForecast[]? forecasts;
protected override async Task OnInitializedAsync()
{
var client = HttpClientFactory.CreateClient("ApiClient");
// 不需要手动加 Token,处理程序已自动附加
forecasts = await client.GetFromJsonAsync<WeatherForecast[]>("weather");
}
public class WeatherForecast
{
public DateTime Date { get; set; }
public int TemperatureC { get; set; }
}
}
常见误区与注意事项
一个容易被忽略的点是:在 Blazor Server 端注入的 HttpClient 如果是单例,而 AuthenticationStateProvider 是作用域,会导致处理器取不到用户。因此 JwtTokenHandler 必须注册为 Scoped 或 Transient,而不能是 Singleton。否则会出现明明已登录,请求却不带 Token 的诡异现象。
另一个误区是在浏览器端把 JWT 存到本地存储后,认为加个处理程序就安全了。实际上 Token 仍可能被 XSS 脚本读取,关键接口应配合 CSP 策略和短时有效期。此外,若 API 和 Blazor 应用跨域,服务端需正确配置 CORS 允许 Authorization 头,否则预检请求会失败。
| 方案 | 优点 | 缺点 |
|---|---|---|
| 组件内手动加 Token | 简单直观,无需额外类 | 重复代码,难维护,刷新易漏 |
| DelegatingHandler 统一加 | 集中管理,组件干净,易扩展 | 需理解管道与生命周期注册 |
| HttpMessageHandler 全局静态 | 性能略高 | 无法感知用户状态,不适用多用户 |
总结
通过 DelegatingHandler 为 Blazor 的 HttpClient 附加 JWT Token,是兼顾可维护性与安全性的标准做法。它将令牌读取、格式组装、过期处理都收敛到一处,业务组件只管调用接口。只要注意处理程序的生命周期注册和 Claim 名称对齐,就能稳定支撑受保护 API 的访问需求。
BlazorHttpClientJWT_Token修改时间:2026-08-06 07:21:30