.NET 8 Blazor交互式Auto组件中HttpClient Cookie认证问题
问题原因
Blazor Server模式运行时,HttpClient在服务器端执行请求,默认不会自动携带当前登录用户的Cookie——服务器端HttpClient的请求上下文与浏览器的用户请求上下文相互独立。而WebAssembly模式下HttpClient运行在浏览器中,会自动附带当前域名下的Cookie,因此能正常通过认证。
解决方案1:配置Server端HttpClient自动携带用户Cookie
通过IHttpContextAccessor获取当前用户的请求上下文,将Cookie添加到HttpClient的请求头中,实现Server模式下的Cookie认证传递,无需修改业务代码。
步骤1:注册IHttpContextAccessor
在Server项目的Program.cs中添加:
builder.Services.AddHttpContextAccessor();
步骤2:自定义Cookie消息处理器
创建一个DelegatingHandler子类,负责将当前用户的Cookie复制到HttpClient请求中:
public class CookieHandler : DelegatingHandler { private readonly IHttpContextAccessor _httpContextAccessor; public CookieHandler(IHttpContextAccessor httpContextAccessor) { _httpContextAccessor = httpContextAccessor; } protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { var httpContext = _httpContextAccessor.HttpContext; if (httpContext != null) { foreach (var cookie in httpContext.Request.Cookies) { request.Headers.Add("Cookie", $"{cookie.Key}={cookie.Value}"); } } return await base.SendAsync(request, cancellationToken); } }
步骤3:注册带Cookie处理器的HttpClient
在Server项目的Program.cs中修改HttpClient注册逻辑:
builder.Services.AddHttpClient("ServerAPI", client => client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress)) .AddHttpMessageHandler(sp => { var httpContextAccessor = sp.GetRequiredService<IHttpContextAccessor>(); return new CookieHandler(httpContextAccessor); });
Client项目的HttpClient注册保持不变即可,WebAssembly模式会自动处理Cookie。
解决方案2:抽象API调用逻辑,区分模式实现
通过定义统一的API服务接口,分别为Server和WebAssembly模式提供不同的实现,既保持业务代码统一,又能避免Server模式下的额外HTTP请求开销。
步骤1:定义统一接口
在共享项目(或Client项目)中创建API服务接口:
public interface IHelloApiService { Task<string> GetHelloAsync(); }
步骤2:实现Server端直接调用版本
在Server项目中实现接口,直接调用后端业务逻辑(无需HTTP请求):
public class ServerHelloApiService : IHelloApiService { // 可注入后端业务服务直接调用,示例模拟返回 public async Task<string> GetHelloAsync() { // 替换为实际业务逻辑调用,比如注入对应的服务类 return await Task.FromResult("hello"); } }
步骤3:实现WebAssembly端HttpClient版本
在Client项目中实现接口,使用HttpClient调用API:
public class ClientHelloApiService : IHelloApiService { private readonly HttpClient _httpClient; public ClientHelloApiService(HttpClient httpClient) { _httpClient = httpClient; } public async Task<string> GetHelloAsync() { return await _httpClient.GetStringAsync("/hello"); } }
步骤4:分别注册服务
- Server项目
Program.cs:
builder.Services.AddScoped<IHelloApiService, ServerHelloApiService>();
- Client项目
Program.cs:
builder.Services.AddScoped<IHelloApiService, ClientHelloApiService>();
步骤5:统一调用逻辑
在Auth.razor中注入IHelloApiService,直接调用方法即可,无需区分运行模式:
@inject IHelloApiService HelloApiService <button @onclick="GetHello">获取Hello</button> <p>@_helloResult</p> @code { private string _helloResult; private async Task GetHello() { _helloResult = await HelloApiService.GetHelloAsync(); } }
方案选择
- 若API逻辑简单,希望最小化代码改动,优先选择方案1,仅需配置HttpClient即可实现统一调用。
- 若API对应复杂的后端业务逻辑,或希望避免Server模式下的内部HTTP请求开销,优先选择方案2,通过抽象层实现更高效的调用。
内容的提问来源于stack exchange,提问作者Francesco Cristallo

