Azure部署SignalR应用出现Handshake Canceled错误求助
Azure SignalR 握手错误排查方案
问题描述
本地开发的集成SignalR的Web应用通信正常,但发布到Azure后出现握手错误:
Error: Connection disconnected with error
'Error: Server returned handshake error: Handshake was canceled.'
已尝试添加Redis背板但问题未解决,同时在Azure中找不到激活WebSockets的选项。
后端代码
public static IServiceCollection AddSignalRWebApi(this IServiceCollection services, IConfiguration configuration) { var redisConnectionString = configuration.GetConnectionString("Redis"); const string channelName = "CustomerPosition"; if (redisConnectionString is null) throw new ArgumentNullException(nameof(redisConnectionString)); services .AddSignalR(options => { options.EnableDetailedErrors = true; options.AddFilter<CustomerInformationFilter>(); options.HandshakeTimeout = TimeSpan.FromSeconds(60); }) .AddStackExchangeRedis(options => { options.ConnectionFactory = async writer => { var config = ConfigurationOptions.Parse(redisConnectionString); config.ChannelPrefix = channelName; var connection = await ConnectionMultiplexer.ConnectAsync(config, writer); connection.ConnectionFailed += async (_, _) => { await RedisBackplaneDisconnectHandler.ReleaseConnections(); }; return connection; }; }); services.AddSingleton<IConnectionsManager, ConnectionsManager>(); services.AddSingleton<CustomerInformationFilter>(); return services; } public static IEndpointRouteBuilder UseSignalR(this IEndpointRouteBuilder endpoints, IApplicationBuilder app) { endpoints.MapHub<CustomerPatrimonyHub>("/sse/customer/patrimony", options => { options.Transports = HttpTransportType.ServerSentEvents; }); RedisBackplaneDisconnectHandler.Configure(app.ApplicationServices.GetRequiredService<IConnectionsManager>()); return endpoints; }
前端代码
const setupConnection = (bearerToken) => { return new Promise(resolve => { connection = new signalR.HubConnectionBuilder() .withUrl(`https://UrlToApplicationInAzure`, { accessTokenFactory: (() => { return bearerToken; }), skipNegotiation: true }) .configureLogging(signalR.LogLevel.Debug) .build(); connection.on("CustomerPatrimonyChanged", (data) => { const customerCodeValueDiv = document.getElementById("customerCodeValue"); customerCodeValueDiv.innerHTML = data.customerCode; const patrimonyValueValueDiv = document.getElementById("patrimonyValue"); patrimonyValueValueDiv.innerHTML = data.patrimony; }); connection.on("Finished", () => { connection.stop(); }); connection.start() .then(() => resolve(connection)) .catch(err => console.error('Start: ', err.toString())); connection.onclose(() => { console.log('OnClose - Disconnected: ', connection.state === signalR.HubConnectionState.Disconnected); }); }) }; const handler = e => { e.preventDefault(); const bearerTokenElement = document.getElementById("bearerToken"); setupConnection(bearerTokenElement.value) .then(conn => { conn.invoke("LoadCustomerPatrimony") .catch(err => console.error('Get Customer Patrimony Update: ', err.toString())); }); }; document.getElementById("submit").addEventListener("click", handler);
排查方案
1. 修复传输协议配置冲突
后端Hub明确限制仅使用ServerSentEvents传输,但前端设置了skipNegotiation: true——该配置仅适用于WebSocket传输(跳过协商直接建立WebSocket连接),二者协议不匹配是握手取消的核心原因。
解决方式二选一:
- 后端放宽传输限制,允许WebSocket:
endpoints.MapHub<CustomerPatrimonyHub>("/sse/customer/patrimony", options => { options.Transports = HttpTransportType.ServerSentEvents | HttpTransportType.WebSockets; }); - 前端关闭
skipNegotiation,允许SignalR通过协商选择SSE传输:.withUrl(`https://UrlToApplicationInAzure`, { accessTokenFactory: (() => { return bearerToken; }) // 移除skipNegotiation: true配置 })
2. Azure中启用WebSockets的操作步骤
登录Azure门户,进入你的App Service实例:
- 点击左侧菜单设置 -> 配置 -> 常规设置
- 找到Web sockets选项,切换为启用,点击顶部「保存」按钮
3. 验证Redis背板连接有效性
- 检查Azure Redis实例的防火墙规则,确保允许当前App Service的IP地址访问
- 确认Redis连接字符串包含正确的主机名、端口和访问密码
- 可在Azure Redis的控制台中执行
PING命令,验证连接是否正常
4. 检查身份令牌有效性
- 确认前端传入的Bearer Token在Azure环境中能正常通过身份验证,检查令牌的受众、颁发者是否与后端配置匹配
- 验证令牌未过期,且包含访问SignalR Hub所需的权限
- 可在后端添加日志,记录握手阶段的令牌验证错误信息
5. 排查Azure App Service超时设置
- 检查App Service应用程序设置中的
WEBSOCKET_CONNECT_TIMEOUT参数,确保超时时间足够(默认120秒,一般无需调整) - 开启App Service的诊断日志,查看是否有更详细的握手失败细节
内容的提问来源于stack exchange,提问作者Erisonson
相关产品推荐
相关产品推荐

