SignalR使用WebSocket无法获取HubCallerContext用户、长轮询断连无法感知怎么办
问题根因说明
- WebSocket模式下拿不到用户ID:
浏览器发起WebSocket握手请求时,无法自定义携带Authorization请求头,你配置的AccessTokenProvider返回的token,SignalR默认会放到请求查询参数的access_token字段中,而服务端JWT认证默认只读取请求头中的token,导致身份认证失败,Context.User为空,自然拿不到用户ID。 - 长轮询模式下感知不到断开:
长轮询的断开检测依赖超时机制,默认客户端异常断开后,服务端需要等待ClientTimeoutInterval时长(默认30秒)才会触发断开事件,如果超时配置不合理或者反向代理提前掐断了连接,就会出现断开事件不触发的问题,实时性远不如WebSocket。
最优解决方案(优先使用WebSocket)
步骤1:修正服务端Hub方法签名
你当前的OnConnectedAsync、OnDisconnectedAsync返回值写错了,多嵌套了一层Task,会导致异步执行异常、上下文丢失,先修正为正确的签名:
// 连接回调正确写法 public override async Task OnConnectedAsync() { ConnectedUserModel connectedUserModel = new ConnectedUserModel() { ConnectionId = Context.ConnectionId, UserId = _userManager.GetUserId(Context.User) }; UserHandler.connectedUsers.Add(connectedUserModel); var temp = new List<string>(); foreach(ConnectedUserModel connectedUser in UserHandler.connectedUsers) { if (!String.IsNullOrEmpty(connectedUserModel.UserId) && temp.Find(x=> x == connectedUserModel.UserId) == null) { temp.Add(connectedUserModel.UserId); await OnConnectAsync(connectedUserModel.UserId); } } await base.OnConnectedAsync(); } // 断开回调正确写法 public override async Task OnDisconnectedAsync(Exception? exception) { var connection = UserHandler.connectedUsers.Find(x => x.ConnectionId == Context.ConnectionId); if (connection != null && !string.IsNullOrEmpty(connection.UserId)) { await OnDisconnectAsync(connection.UserId); UserHandler.connectedUsers.Remove(connection); } await base.OnDisconnectedAsync(exception); }
步骤2:配置服务端JWT认证支持查询参数读取
在JWT认证配置中添加OnMessageReceived事件,将SignalR请求中查询参数的access_token赋值给认证上下文:
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { // 保留你原有JWT配置,比如Issuer、Audience、校验规则等 options.Events = new JwtBearerEvents { OnMessageReceived = context => { var accessToken = context.Request.Query["access_token"]; var path = context.HttpContext.Request.Path; // 替换为你实际的Hub路由前缀 if (!string.IsNullOrEmpty(accessToken) && path.StartsWithSegments("/signalr/hub")) { context.Token = accessToken; } return Task.CompletedTask; } }; });
步骤3:修正客户端配置
删除手动添加Authorization头的代码,AccessTokenProvider已经可以自动适配不同传输方式处理token携带逻辑,手动加头会导致冲突:
HubConnection = new HubConnectionBuilder().WithUrl(Config.BaseUrl + ApplicationConstants.SignalR.HubUrl, options => { options.Transports = Microsoft.AspNetCore.Http.Connections.HttpTransportType.WebSockets; options.AccessTokenProvider = async () => await Task.FromResult(NPCompletApp.Token); }).WithAutomaticReconnect().Build();
步骤4:确认中间件顺序与反向代理配置
- 服务端中间件顺序必须为:
UseRouting→UseAuthentication→UseAuthorization→MapHub,顺序错误会导致身份认证不生效。 - 如果使用Nginx、IIS等反向代理,需要配置支持WebSocket协议转发,以Nginx为例:
location /signalr/hub { proxy_pass http://你的后端服务地址; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Host $host; # 调整超时时间避免WebSocket被提前断开 proxy_read_timeout 300s; proxy_send_timeout 300s; }
长轮询兼容方案(如确实无法使用WebSocket时选择)
调整SignalR超时配置保证断开事件正常触发:
builder.Services.AddSignalR(options => { // 客户端超时时间,超过该时间未收到客户端消息判定为断开 options.ClientTimeoutInterval = TimeSpan.FromSeconds(30); // 服务端心跳间隔,定时向客户端发送心跳包保活 options.KeepAliveInterval = TimeSpan.FromSeconds(15); });
该方案断开事件仍会有最多30秒的延迟,仅作为备选。
内容的提问来源于stack exchange,提问作者omar wasfi
相关产品推荐
相关产品推荐

