Blazor中使用IHttpClientFactory添加Cookie:登录后Header Propagation失效问题
场景描述
我有一个依赖外部API的Blazor项目,登录时调用API获取会话令牌并存储到Claim中,要求该令牌以Cookie形式随每次请求发送。我采用Header Propagation结合IHttpClientFactory实现,配置代码如下:
services.AddHttpClient<ServiceClient>(client => { client.BaseAddress = new Uri(url); client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); }) .ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler() { ClientCertificateOptions = ClientCertificateOption.Manual, ServerCertificateCustomValidationCallback = (httpRequestMessage, cert, cetChain, policyErrors) => { return true; }, }) .AddHeaderPropagation(options => { options.Headers.Add("Cookie"); }); services.AddHeaderPropagation(options => options.Headers.Add("Cookie", context => { var identity = context.HttpContext.User.Identities.FirstOrDefault(x => x.AuthenticationType == "AppAuthenticationType"); if (identity?.IsAuthenticated ?? false) return $"SESSION={identity.GetSession()}"; return String.Empty; });
中间件配置在app.UseAuthentication();和app.UseAuthorization();之后添加了app.UseHeaderPropagation();。
遇到的问题:首次打开网站未登录时Claim未设置;登录后设置了Claim,但Header Propagation未更新,令牌未随请求发送。
解决方案
1. 移除重复的Header Propagation配置
你的代码中存在两处AddHeaderPropagation配置:一处在HttpClient注册时添加了默认的Cookie头,另一处全局配置了自定义的Cookie头生成逻辑。这会导致头信息的生成逻辑冲突,且默认配置不会动态更新用户认证状态。
删除HttpClient注册中的AddHeaderPropagation(options => options.Headers.Add("Cookie")),仅保留全局的自定义配置,确保每次请求都执行委托获取当前用户的令牌:
services.AddHttpClient<ServiceClient>(client => { client.BaseAddress = new Uri(url); client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); }) .ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler() { ClientCertificateOptions = ClientCertificateOption.Manual, ServerCertificateCustomValidationCallback = (httpRequestMessage, cert, cetChain, policyErrors) => true, }) .AddHeaderPropagation(); // 仅启用Header Propagation,不再指定默认Cookie头 services.AddHeaderPropagation(options => { options.Headers.Add("Cookie", context => { var identity = context.HttpContext.User.Identities.FirstOrDefault(x => x.AuthenticationType == "AppAuthenticationType"); if (identity?.IsAuthenticated ?? false) { // 确保GetSession()方法正确从Claim中提取令牌,这里可以显式写出Claim类型避免歧义 var sessionToken = identity.Claims.FirstOrDefault(c => c.Type == "SessionToken")?.Value; return !string.IsNullOrEmpty(sessionToken) ? $"SESSION={sessionToken}" : string.Empty; } return string.Empty; }); });
2. 确认中间件顺序正确性
确保UseHeaderPropagation的位置在UseAuthentication和UseAuthorization之后,且在UseRouting和UseEndpoints之前,这样中间件才能获取到经过认证的用户上下文:
app.UseAuthentication(); app.UseAuthorization(); app.UseHeaderPropagation(); app.UseRouting(); app.UseEndpoints(endpoints => { endpoints.MapBlazorHub(); endpoints.MapFallbackToPage("/_Host"); });
3. 验证Claim提取逻辑
确保GetSession()扩展方法(或显式的Claim提取逻辑)能正确从用户Identity中获取会话令牌。建议显式指定Claim类型,避免因Claim名称不匹配导致令牌无法获取。
4. Blazor Server额外注意事项
在Blazor Server中,确保ServiceClient是通过依赖注入获取的Scoped服务(默认AddHttpClient注册的客户端是Transient,但底层的HttpMessageHandler是池化的,HeaderPropagationHandler会在每次请求时从当前HttpContext获取头信息)。组件中调用API时,直接注入ServiceClient即可保证使用当前用户的上下文。
内容的提问来源于stack exchange,提问作者Antonio Rodríguez

