You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.04 18:43:17