如何解决SignalR中基于Cookie的身份验证HttpContext.User为空问题
背景情况
我有一个Asp.Net Core Web API + Angular前端的项目,已配置基于Cookie的身份验证,在Web API控制器中访问HttpContext.User能正常获取用户信息。项目基于Asp.Net Core Identity实现身份识别与授权。
当前认证配置代码:
services.AddAuthentication(opt => { opt.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; opt.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; }).AddJwtBearer(options => { options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = true, ValidateAudience = true, ValidateLifetime = true, ValidateIssuerSigningKey = true, ValidIssuer = tokenSettings.ValidIssuer, ValidAudience = tokenSettings.ValidAudience, IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(tokenSettings.SecurityKey)) }; });
中间件配置:
app.UseAuthentication(); app.UseAuthorization();
为了验证用户是否有权限打开对应资源的SignalR WebSocket,我重写了Hub的OnConnectedAsync方法:
public override async Task OnConnectedAsync() { string dashboardId = Context.GetHttpContext().Request.Query["dashboardId"]; string connectionId = Context.ConnectionId; await _mediator.Send(new StartDashboardDataAcquisitionCommand(Context.GetHttpContext().User, Guid.Parse(dashboardId))); await base.OnConnectedAsync(); }
客户端初始化代码:
this.connection = new signalR.HubConnectionBuilder() .withUrl(environment.backendUrl + 'dashboards/data?dashboardId=' + dashboardId) .withAutomaticReconnect() .build(); this.connection.on('SendDashboardData', dataBatch => { this.store.dispatch(new SetActiveDashboardData(dataBatch as DataBatch)); }); this.connection.start().catch(err => { console.error(err); });
问题:命令处理器验证权限时,发现Context.GetHttpContext().User为空,浏览器不是会自动发送Cookie吗?请问遗漏了什么配置?
排查与解决
核心矛盾点
你的认证配置是JWT Bearer认证方案,但你实际使用的是Cookie身份验证,两者不匹配。系统只会按照默认的JWT Bearer方案去解析请求中的令牌,不会处理Cookie中的身份信息,导致SignalR连接请求未被正确认证,User对象为空。
具体解决步骤
调整认证方案配置,匹配Cookie认证
如果你用Asp.Net Core Identity,默认的认证方案是Cookie(IdentityConstants.ApplicationScheme),需要修改默认认证方案为Cookie:services.AddAuthentication(opt => { opt.DefaultAuthenticateScheme = IdentityConstants.ApplicationScheme; opt.DefaultChallengeScheme = IdentityConstants.ApplicationScheme; }) // 若需同时支持JWT,可保留JWT配置,但默认方案设为Cookie .AddJwtBearer(options => { options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = true, ValidateAudience = true, ValidateLifetime = true, ValidateIssuerSigningKey = true, ValidIssuer = tokenSettings.ValidIssuer, ValidAudience = tokenSettings.ValidAudience, IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(tokenSettings.SecurityKey)) }; });注:Asp.Net Core Identity调用
AddIdentity/AddDefaultIdentity时会自动添加Cookie认证,无需手动AddCookie,只需确保默认方案正确。确保跨域场景下允许携带凭据
如果前端和后端是跨域部署,需配置CORS允许凭据,并在前端SignalR连接中开启withCredentials:- 后端CORS配置:
services.AddCors(options => { options.AddPolicy("AllowAngular", policy => { policy.WithOrigins(environment.FrontendUrl) .AllowAnyHeader() .AllowAnyMethod() .AllowCredentials(); // 必须开启,允许携带Cookie }); }); // 中间件顺序:UseCors必须在UseAuthentication之前 app.UseCors("AllowAngular"); - 前端SignalR配置:
this.connection = new signalR.HubConnectionBuilder() .withUrl(environment.backendUrl + 'dashboards/data?dashboardId=' + dashboardId, { withCredentials: true // 携带Cookie凭据 }) .withAutomaticReconnect() .build();
- 后端CORS配置:
检查中间件顺序
中间件顺序必须严格遵循以下顺序,否则认证逻辑无法正常执行:app.UseHttpsRedirection(); app.UseStaticFiles(); app.UseRouting(); app.UseCors(); // 必须在认证之前 app.UseAuthentication(); app.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints.MapControllers(); endpoints.MapHub<YourDashboardHub>("/dashboards/data"); // 确保Hub路由正确配置 });简化Hub中用户信息的获取
SignalR的Hub.Context直接包含用户信息,无需通过GetHttpContext()获取,可直接使用Context.User:public override async Task OnConnectedAsync() { string dashboardId = Context.GetHttpContext().Request.Query["dashboardId"]; await _mediator.Send(new StartDashboardDataAcquisitionCommand(Context.User, Guid.Parse(dashboardId))); await base.OnConnectedAsync(); }
内容的提问来源于stack exchange,提问作者J4N

