websocket连接返回403并非业务鉴权失败,而是upgrade请求被网关或waf拦截;需检查nginx三行配置、waf放行规则、token应通过url参数传递而非header。

Python WebSocket连接返回403,先确认是不是鉴权阶段被拦
WebSocket握手失败时返回403,和HTTP API的403不是一回事——它大概率发生在Upgrade请求被网关或WAF拒绝,而不是服务端业务逻辑校验失败。关键看响应体:error code: 1010 是Cloudflare等WAF拦截;{"error":{"type":"authentication_error"}} 才是Token无效。Python客户端(如websocket-client)默认不带User-Agent,容易被WAF当成爬虫直接封掉,而curl有默认UA所以能过。
用curl -v对比原始握手请求头
把Python发的请求“翻译”成curl,逐项比对关键字段:
-
Upgrade: websocket和Connection: Upgrade必须同时存在且大小写严格匹配 -
Sec-WebSocket-Key是客户端随机生成的,每次不同,但服务端必须用它算出Sec-WebSocket-Accept -
Sec-WebSocket-Version: 13缺失或值错误会导致400,不是403 - 如果用Token鉴权,检查是否误塞进
Sec-WebSocket-Protocol——这个字段只用于子协议协商,不是传凭证的地方
Python里别用websocket-client传Header做鉴权
浏览器原生WebSocket构造函数不支持自定义header,websocket-client库虽能强行加,但多数网关(Nginx、Cloudflare)在握手阶段只信任URL参数或特定header(如Authorization),且要求header在proxy_set_header里显式透传。更稳妥的做法是:
- 前端先调
/api/v1/ws-token换一个短期专用Token - 用
new WebSocket("wss://host/ws?token=xxx")方式传参 - Python客户端也走URL传参:
ws = websocket.WebSocketApp("wss://host/ws?token=xxx") - 服务端从
req.url或request.args.get("token")提取并校验,别依赖header
服务端日志里重点盯Upgrade请求的响应状态码和头
很多WebSocket框架(如FastAPI + starlette.websockets、Flask-SocketIO)在握手失败时只打一行“client disconnected”,实际原因藏在反向代理或WAF日志里。务必确认:
- Nginx access log中该请求的status是否为
403,且$upstream_http_content_type为空或为text/html(WAF拦截典型特征) - 服务端收到的
request.headers里有没有Sec-WebSocket-Key——没有说明代理根本没透传 - 如果用了Redis吊销Token,检查
GET ws:token:xxx是否返回nil,避免缓存穿透导致误判
真正卡住的地方往往不在Python代码里,而在Nginx那三行配置漏了一行,或者Cloudflare的WAF规则把空User-Agent默认干掉了。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











