Files
wecom_it_smart_desk/docs/ADRs/ADR-002-WS-Token-Subprotocol鉴权.md
T
Simon 64d6812ec3 fix: P0遗留修复 + ADR/SOP文档
- requirements.txt: 添加 passlib[bcrypt] 依赖
- deploy-server/nginx.conf: /ws/ 路径添加 access_log off
- docs/ADRs/: 新增 4 个 ADR 决策记录
- docs/SOPs/: 新增 4 个 SOP 操作规程
2026-06-15 00:03:11 +08:00

2.5 KiB

ADR-002: WebSocket Token 鉴权(走 Sec-WebSocket-Protocol)

状态: 已采纳 日期: 2026-06-14 决策者: 宋献 + Claude 评审 关联: 风险跟踪表 第十节 / 评审报告 workbuddy-2026-06-14-P0安全.md


1. 背景

WebSocket 鉴权原方案:ws://server/ws/?token=<JWT> —— token 在 URL 里:

  • 被 nginx access_log 记录
  • 被 CDN / 反代记录
  • 被浏览器历史记录

P0 漏洞(H-11 风险项),已修复。

2. 评估方案

方案 浏览器支持 token 泄露 实施难度 结论
A. Authorization: Bearer header 浏览器 WS API 不支持自定义 header 不泄 否决(浏览器限制)
B. Sec-WebSocket-Protocol: bearer. 现代浏览器都支持 不在 URL 采纳
C. 第一条消息传 token 全支持 ⚠️ 需先开 WS 接受任意连接(无法鉴权) 否决
D. Cookie 自动带 全支持 ⚠️ CSRF 风险 否决

3. 决策

采纳 B 方案: Sec-WebSocket-Protocol: bearer.<token>

服务端协商 subprotocol,客户端用第二个 subprotocol 传 token(浏览器 API new WebSocket(url, [subprotocols]))。

4. 实现

4.1 前端

// frontend-agent/src/composables/useWebSocket.ts
const ws = new WebSocket(wsUrl, [`bearer.${agentStore.token}`])

4.2 后端

# backend/app/api/ws.py
subprotocol = request.headers.get("sec-websocket-protocol", "")
if subprotocol.startswith("bearer."):
    token = subprotocol[7:]
else:
    # 降级:Authorization header
    auth = request.headers.get("Authorization", "")
    if auth.startswith("Bearer "):
        token = auth[7:]
    else:
        # 降级:query param(已废,只用于兼容旧前端)
        token = request.query_params.get("token", "")

5. 降级路径

优先级 来源 用途
1 Sec-WebSocket-Protocol 标准(主)
2 Authorization: Bearer Postman / 测试工具
3 query ?token= 已废(留兼容)

6. 风险与缓解

风险 缓解
浏览器 API 不支持 subprotocol 现代浏览器(2020+)都支持,无问题
旧客户端不更新 query param 降级仍可用,但提示更新
nginx 仍记录 subprotocol location /ws/ { access_log off; } 配合

7. 决策影响

  • WS 鉴权修复,token 不再泄
  • nginx access_log 关闭,旧 token 不留痕
  • ⚠️ 旧客户端需更新(发版通知)