fix: WebSocket 鉴权 — Cookie 优先 (支持 HttpOnly)

根因: panel_token 是 HttpOnly cookie, JS getCookie() 读不到
→ 前端 WS ?token= 参数为空 → 认证失败 → 仪表盘无数据

修复:
- _ws_auth_wrapper: Cookie 优先 → query token 备用 → API Key
- 浏览器同源 WS 自动发送 Cookie, 无需 ?token=
- dashboard.js / logs.js 移除多余的 getCookie+?token=

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
qinglong
2026-06-13 17:45:58 +08:00
parent 557135c376
commit 5e06c17b52
7 changed files with 84 additions and 9 deletions
+56
View File
@@ -0,0 +1,56 @@
framework:
debug: true
name: SenSu
version: v0.6.0
logging:
debug_level_file: true
level: DEBUG
max_file_size: 10MB
max_log_files: 20
plugins:
auto_load: true
hot_reload: true
max_retry_count: 3
isolation: true
# 自动启动脚本 — 框架启动时后台拉起
auto_start_scripts:
enabled: true
scripts: []
# 示例:
# - name: cyrene_debug
# path: ~/cyrene_debug_server.py
# enabled: true
# args: []
# cwd: ~
# TUI配置
tui:
enabled: true
refresh_rate: 30
# TUI布局配置
layout:
grid_rows: "4fr 5fr 1fr" # 三行布局:日志区域、消息区域、输入区域的比例
# TUI样式配置
styles:
log_area: "border: solid green; overflow-y: auto;"
message_area: "border: solid yellow; overflow-y: auto;"
input_area: "border: solid red;"
# TUI日志显示配置
log_display:
max_lines: 200
# 互联网服务配置
internet:
websocket:
host: "0.0.0.0"
port: 4240
# 其他websocket配置...
http:
host: "0.0.0.0"
port: 4200
# 其他http配置...
panel:
entrance:
path: "/SenSu"
username: "admin"
password: "admin"
+11
View File
@@ -42,5 +42,16 @@
"framework.command.execute" "framework.command.execute"
], ],
"timestamp": 17177.106703342 "timestamp": 17177.106703342
},
"fe262a42": {
"plugin_name": "sentinel",
"permissions": [
"plugin.sentinel.read",
"plugin.sentinel.write",
"plugin.network.access",
"framework.event.subscribe",
"framework.command.execute"
],
"timestamp": 25770.11842246
} }
} }
+1 -1
View File
@@ -93,7 +93,7 @@ commands:
permissions: permissions:
- framework.command.test - framework.command.test
source: internal source: internal
last_updated: 17177.060053342 last_updated: 25770.099665272
plugin_commands: plugin_commands:
sentinel: sentinel:
sentinel: *id001 sentinel: *id001
+1 -1
View File
@@ -1,5 +1,5 @@
http_port: 4200 http_port: 4200
last_updated: 17177.1104973 last_updated: 25770.118859595
plugin_routes: plugin_routes:
sentinel: sentinel:
- methods: - methods:
+12 -2
View File
@@ -13,11 +13,21 @@ logger = logging.getLogger(__name__)
_sys_ws_clients: set = set() _sys_ws_clients: set = set()
def _ws_auth_wrapper(handler): def _ws_auth_wrapper(handler):
"""WebSocket 鉴权包装从 query string 取 token 验证""" """WebSocket 鉴权 — Cookie 优先(同源自动发送), query token 备用"""
async def wrapper(request): async def wrapper(request):
# 1. Cookie (浏览器同源自动发送, 支持 HttpOnly)
token = request.cookies.get("panel_token", "")
# 2. Query string token 备用 (跨域/非浏览器客户端)
if not token:
token = request.query.get("token", "") token = request.query.get("token", "")
# 3. API Key 验证
session_store = request.app.get("panel_session_store", {}) session_store = request.app.get("panel_session_store", {})
if not token or token not in session_store: is_valid = token and token in session_store
if not is_valid and token:
from services.web_panel.routes.apikeys import validate_api_key
is_valid = validate_api_key(token) is not None
if not is_valid:
ws = web.WebSocketResponse() ws = web.WebSocketResponse()
await ws.prepare(request) await ws.prepare(request)
await ws.send_str(json.dumps({"error": "Unauthorized"})) await ws.send_str(json.dumps({"error": "Unauthorized"}))
+1 -2
View File
@@ -32,8 +32,7 @@ window.DashboardModule = {
} }
var base = window.location.pathname.split("/").slice(0, 2).join("/"); var base = window.location.pathname.split("/").slice(0, 2).join("/");
var tok = getCookie("panel_token"); var ws = new WebSocket("ws://" + location.host + base + "/api/system/ws");
var ws = new WebSocket("ws://" + location.host + base + "/api/system/ws?token=" + (tok || ""));
self.ws = ws; self.ws = ws;
ws.onopen = function() { ws.onopen = function() {
+1 -2
View File
@@ -25,8 +25,7 @@ window.LogsModule = {
} }
var base = window.location.pathname.split("/").slice(0, 2).join("/"); var base = window.location.pathname.split("/").slice(0, 2).join("/");
var tok = getCookie("panel_token"); var ws = new WebSocket("ws://" + location.host + base + "/api/logs/ws");
var ws = new WebSocket("ws://" + location.host + base + "/api/logs/ws?token=" + (tok || ""));
self.ws = ws; self.ws = ws;
ws.onopen = function() { ws.onopen = function() {