Blazor Server与.NET 6 API认证Cookie串户问题求助
核心问题分析
从你的代码和描述来看,最可能导致串户的原因是HttpClientHandler的生命周期配置错误,其次是中间件顺序、sessionid获取方式等问题,以下是具体排查和修复步骤:
1. 修复HttpClientHandler共享问题(最优先)
你在Blazor Server的Program.cs中设置了SetHandlerLifetime(Timeout.InfiniteTimeSpan),这会让HttpClientHandler成为单例对象。而Blazor Server中HttpClient是Scoped的,多个用户的请求会共享同一个Handler的CookieContainer,直接导致不同用户的认证Cookie被混用,出现串户。
修复代码:
移除无限生命周期设置,恢复默认Handler生命周期:
builder.Services.AddHttpClient(Microsoft.Extensions.Options.Options.DefaultName, options => { options.BaseAddress = new Uri(ProprietaryAPI.baseURI); });
如果需要自定义Handler,确保每个用户拥有独立的Handler实例:
builder.Services.AddScoped<HttpClientHandler>(); builder.Services.AddHttpClient(Microsoft.Extensions.Options.Options.DefaultName, (sp, client) => { client.BaseAddress = new Uri(ProprietaryAPI.baseURI); }).ConfigurePrimaryHttpMessageHandler(sp => sp.GetRequiredService<HttpClientHandler>());
2. 修正sessionid获取方式
你当前通过HttpContext.Request.Headers.TryGetValue("sessionid", out var val)获取sessionid,这种方式依赖前端手动传递Header,容易出错且不安全。正确的做法是从认证后的ClaimsPrincipal中读取,因为登录时你已经将sessionid添加到Claims中:
修复代码(API端):
// 替换原来的Header获取方式 var sessionId = HttpContext.User.FindFirstValue("sessionid"); if (!string.IsNullOrEmpty(sessionId)) { // 使用sessionid获取用户信息 } else { // 未认证处理逻辑 }
3. 调整Blazor Server中间件顺序
你的Blazor Server中间件顺序错误,UseRouting应该放在UseHttpsRedirection、UseStaticFiles之后,UseAuthentication、UseAuthorization之前,否则认证信息无法正确传递到Blazor Hub。
修复后的中间件顺序:
var app = builder.Build(); if (!app.Environment.IsDevelopment()) { app.UseExceptionHandler("/Error"); app.UseHsts(); } app.UseHttpsRedirection(); app.UseStaticFiles(); app.UseCookiePolicy(); app.UseRouting(); app.UseAuthentication(); app.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints.MapRazorPages(); endpoints.MapBlazorHub(); endpoints.MapFallbackToPage("/_Host"); }); app.Run();
4. 优化API的Cookie配置
为避免Cookie冲突,给认证Cookie设置唯一名称,并强化安全属性:
builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme) .AddCookie(options => { options.LoginPath = "/api/Proprietary/NewLogin"; options.Cookie.SameSite = SameSiteMode.None; options.Cookie.SecurePolicy = CookieSecurePolicy.Always; options.Cookie.Name = "MyAppAuthCookie"; // 设置唯一Cookie名称 options.Cookie.HttpOnly = true; // 防止XSS攻击 options.Cookie.IsEssential = true; // 无需用户同意 });
5. 检查CustomAuthStateProvider实现
确保你的CustomAuthStateProvider是Scoped生命周期,且能正确响应用户登录状态变化:
public class CustomAuthStateProvider : AuthenticationStateProvider { private readonly AuthService _authService; public CustomAuthStateProvider(AuthService authService) { _authService = authService; _authService.OnAuthStateChanged += () => NotifyAuthenticationStateChanged(GetAuthenticationStateAsync()); } public override async Task<AuthenticationState> GetAuthenticationStateAsync() { var sessionId = await _authService.GetSessionIdAsync(); if (string.IsNullOrEmpty(sessionId)) { return new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity())); } var claims = new List<Claim> { new Claim("sessionid", sessionId) }; var identity = new ClaimsIdentity(claims, "CustomAuth"); return new AuthenticationState(new ClaimsPrincipal(identity)); } }
在AuthService登录成功后,触发OnAuthStateChanged事件,确保Provider及时更新当前用户的认证状态。
6. Azure环境配置检查
- 开启会话亲和性:在Azure Portal的App Service -> 配置 -> 常规设置中,将「会话亲和性」设置为开启,确保Blazor Server的SignalR连接始终路由到同一实例,避免状态丢失。
- 多实例一致性:如果App Service使用多实例部署,确保所有实例的配置一致,无共享缓存或状态的逻辑。
内容的提问来源于stack exchange,提问作者willjacobball

