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

React+ASP.NET Core WebSocket传Token握手失败问题求助

问题

我正在开发一个React应用,需与ASP.NET Core后端建立WebSocket连接实现文件上传功能。后端要求身份验证,我尝试通过react-use-websocket库的useWebSocket钩子将认证Token作为子协议传递,但连接失败;移除Token后连接正常。

前端代码

import React, { useState } from 'react';
import useWebSocket from 'react-use-websocket';

function App() {
  const socketUrl = "ws://localhost:5204/api/v1/upload/file?projectId=102";
  const token = "my-token";

  const { sendMessage, readyState } = useWebSocket(socketUrl, {
    protocols: ['realProtocol', token],
  });

  // 剩余组件代码...
}

报错信息

System.Net.WebSockets.WebSocketException: 'The remote party closed the WebSocket connection without completing the close handshake.'

后端代码

[Route("file")]
[AllowAnonymous]
public async Task<object> UploadListOfFiles(int projectId)
{
    try
    {
        if (HttpContext.WebSockets.IsWebSocketRequest)
        {
            // 获取Sec-WebSocket-Protocol头的值
            var protocolHeader = HttpContext.Request.Headers["Sec-WebSocket-Protocol"];
            var token = protocolHeader.FirstOrDefault()?.Replace("realProtocol, ", "");
            var authenticationResult = await _customJwtBearerHandler.HandleTokenAuthenticationAsync(token);

            if (authenticationResult.Succeeded)
            {
                var email = _customJwtBearerHandler.ExtractEmailFromToken(token).ToLower();

                using var webSocket = await HttpContext.WebSockets.AcceptWebSocketAsync();

                var processedFiles = await _fileUploadClient.ReceiveFileChunks(HttpContext, webSocket);

                List<FileUploadValidationResultModel> validationResults = await _validators.Validate(processedFiles, projectId);

                List<ApiResponse> responses = await _fileUploadClient.ValidateAndProcessUpload(email, validationResults, projectId);

                return responses;
            }
            else
            {
                return StatusCodes.Status401Unauthorized;
            }
        }
        else
        {
            return StatusCodes.Status400BadRequest;
        }

    }
}
原因分析

WebSocket子协议的核心规则是:客户端发送的子协议列表中,服务器必须选择其中一个并返回给客户端,否则客户端会判定连接协商失败,主动关闭连接。

你的代码存在两个关键问题:

  1. 前端传递了['realProtocol', token]两个子协议,但后端调用AcceptWebSocketAsync()时未指定要接受的子协议,ASP.NET Core默认返回空的子协议头,客户端因无匹配协议断开连接。
  2. 后端通过Replace("realProtocol, ", "")提取Token的方式鲁棒性极差,一旦子协议顺序变化、格式调整(比如无空格),就会导致Token提取错误,触发认证失败。
解决方案

方案1:修正子协议处理逻辑(不推荐,仅适配当前写法)

前端调整

将Token嵌入自定义子协议中,避免传递多个子协议:

const { sendMessage, readyState } = useWebSocket(socketUrl, {
  protocols: [`realProtocol;token=${token}`],
});

后端调整

解析自定义子协议提取Token,并确保返回客户端请求的子协议:

if (HttpContext.WebSockets.IsWebSocketRequest)
{
    var protocolHeader = HttpContext.Request.Headers["Sec-WebSocket-Protocol"].FirstOrDefault();
    string token = null;
    string selectedProtocol = null;
    
    if (!string.IsNullOrEmpty(protocolHeader))
    {
        var parts = protocolHeader.Split(';');
        selectedProtocol = parts[0];
        // 遍历提取Token
        foreach (var part in parts)
        {
            if (part.Trim().StartsWith("token="))
            {
                token = part.Substring("token=".Length).Trim();
                break;
            }
        }
    }

    var authenticationResult = await _customJwtBearerHandler.HandleTokenAuthenticationAsync(token);
    if (authenticationResult.Succeeded)
    {
        // 必须返回客户端请求的子协议,完成协商
        using var webSocket = await HttpContext.WebSockets.AcceptWebSocketAsync(selectedProtocol);
        
        var email = _customJwtBearerHandler.ExtractEmailFromToken(token).ToLower();
        var processedFiles = await _fileUploadClient.ReceiveFileChunks(HttpContext, webSocket);
        List<FileUploadValidationResultModel> validationResults = await _validators.Validate(processedFiles, projectId);
        List<ApiResponse> responses = await _fileUploadClient.ValidateAndProcessUpload(email, validationResults, projectId);

        return responses;
    }
    else
    {
        return StatusCodes.Status401Unauthorized;
    }
}

方案2:标准Token传递方式(推荐)

子协议的设计初衷是定义WebSocket消息格式,并非传递认证信息。更合规的方式是通过请求头传递Token:

前端调整

通过headers配置Authorization头:

const { sendMessage, readyState } = useWebSocket(socketUrl, {
  protocols: ['realProtocol'],
  headers: {
    Authorization: `Bearer ${token}`
  }
});

后端调整

从请求头提取Token,并指定接受的子协议:

if (HttpContext.WebSockets.IsWebSocketRequest)
{
    // 从Authorization头提取Token
    var token = HttpContext.Request.Headers["Authorization"].FirstOrDefault()?.Replace("Bearer ", "");
    var authenticationResult = await _customJwtBearerHandler.HandleTokenAuthenticationAsync(token);

    if (authenticationResult.Succeeded)
    {
        var email = _customJwtBearerHandler.ExtractEmailFromToken(token).ToLower();
        // 指定接受的子协议,完成协商
        using var webSocket = await HttpContext.WebSockets.AcceptWebSocketAsync("realProtocol");
        
        var processedFiles = await _fileUploadClient.ReceiveFileChunks(HttpContext, webSocket);
        List<FileUploadValidationResultModel> validationResults = await _validators.Validate(processedFiles, projectId);
        List<ApiResponse> responses = await _fileUploadClient.ValidateAndProcessUpload(email, validationResults, projectId);

        return responses;
    }
    else
    {
        return StatusCodes.Status401Unauthorized;
    }
}

内容的提问来源于stack exchange,提问作者yatin tripathi

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.30 07:59:56