# SenSu API 认证与数据访问流程 > 版本: v0.7.0+ > 更新: 2026-06-13 --- ## 一、概述 所有 API 端点(除 `/health` 和 `/SenSu/api/login`)均需要认证。认证基于 **Panel Session Token** 机制: - 登录后获取 32 字符随机 token - 后续请求通过 Cookie (`panel_token`) 或 WebSocket URL 参数 (`?token=`) 传递 - 写操作额外需要 `X-CSRF-Token` 请求头 --- ## 二、登录流程 ``` POST /SenSu/api/login Content-Type: application/json {"username": "admin", "password": "admin"} ``` ### 处理步骤 | 步骤 | 说明 | 代码位置 | |------|------|---------| | 1 | **频率检查** — IP 是否因 5 次失败被锁定 60s | `auth.py:_check_rate_limit()` | | 2 | **哈希比较** — `SHA256(password + salt) == stored_hash` | `manager.py:_hash_pw()` | | 3 | **生成 token** — `secrets.token_hex(16)` → 32 字符 | `auth.py:handle_login()` | | 4 | **存入内存** — `PANEL_SESSION_STORE[token] = {username, perms, login_time}` | 同上 | | 5 | **持久化到 DB** — `sensu_db.config_kv['panel_sessions']` JSON | `auth.py:_save_sessions_to_db()` | | 6 | **设置 Cookie** — `panel_token={token}; HttpOnly; Max-Age=259200; SameSite=Lax` | 同上 | ### 响应 ```json {"success": true, "username": "admin"} ``` ### 安全特性 - 密码使用 `secrets.compare_digest()` 时序安全比较 - 每用户独立随机盐 `secrets.token_hex(16)` - 5 次/IP 失败后锁定 60 秒,返回 `429 Too Many Requests` - Cookie 设置 `HttpOnly` (JS 不可读) + `SameSite=Lax` --- ## 三、HTTP API 请求(读操作) ``` GET /SenSu/api/files/list Cookie: panel_token=f93f18546742b6c4... ``` ### 鉴权流程 (`panel_auth()`) ``` request │ ├─ 1. 从 Cookie 取 panel_token │ (或 Authorization: Bearer xxx header) │ ├─ 2. 查 PANEL_SESSION_STORE[token] │ ├─ 找到 → 注入 request['user'] │ └─ 未找到 → 401 {"error": "未认证或会话已过期"} │ └─ 3. handler(request) → JSON 响应 ``` ### 受保护的读端点 | 端点 | 说明 | |------|------| | `GET /SenSu/api/files/list` | 文件列表 | | `GET /SenSu/api/files/info` | 文件信息 | | `GET /SenSu/api/files/read` | 读取文本 | | `GET /SenSu/api/files/download` | 下载文件 | | `GET /SenSu/api/files/picker` | 文件选择器 | | `GET /SenSu/api/projects` | 项目列表 | | `GET /SenSu/api/projects/{name}/logs` | 项目日志 | | `GET /SenSu/api/proxy` | 代理列表 | | `GET /SenSu/api/system` | 系统状态 | | `GET /SenSu/api/framework` | 框架状态 | | `GET /SenSu/api/plugins` | 插件列表 | | `GET /SenSu/api/commands` | 命令列表 | | `GET /SenSu/api/auth/status` | 认证状态 | | `GET /plugin/{name}` | 插件页面 | | `GET /plugin/{name}/sse` | 插件 SSE | --- ## 四、HTTP API 请求(写操作 — CSRF 保护) ``` POST /SenSu/api/files/delete Cookie: panel_token=f93f18546742b6c4... X-CSRF-Token: f93f18546742b6c4... Content-Type: application/json {"path": "/some/file"} ``` ### 鉴权流程 (`panel_auth(csrf_protect=True)`) ``` request │ ├─ 1-2. 同上 (token 验证) │ ├─ 3. CSRF 检查 │ X-CSRF-Token header == panel_token cookie ? │ ├─ 匹配 → 继续 │ └─ 不匹配 → 403 {"error": "CSRF 验证失败"} │ └─ 4. handler(request) → JSON 响应 ``` ### CSRF 保护的写端点 | 端点 | 说明 | |------|------| | `POST /SenSu/api/files/delete` | 删除文件/目录 | | `POST /SenSu/api/files/write` | 写入文件 | | `POST /SenSu/api/files/mkdir` | 创建目录 | | `POST /SenSu/api/files/touch` | 创建文件 | | `POST /SenSu/api/files/rename` | 重命名 | | `POST /SenSu/api/files/upload` | 上传文件 | | `POST /SenSu/api/projects/run` | 启动项目 | | `POST /SenSu/api/projects/{name}/stop` | 停止项目 | | `POST /SenSu/api/projects/{name}/stdin` | 向项目发送输入 | | `POST /SenSu/api/proxy` | 添加代理 | | `DELETE /SenSu/api/proxy/{path}` | 删除代理 | --- ## 五、WebSocket 连接 ``` GET /SenSu/api/system/ws?token=f93f18546742b6c4... Upgrade: websocket ``` ### 鉴权流程 (`_ws_auth_wrapper`) ``` WebSocket 握手请求 │ ├─ 1. 从 query string 取 token │ ├─ 2. 查 PANEL_SESSION_STORE[token] │ ├─ 找到 → 建立 WS 连接 → 每 2s 推送 │ └─ 未找到 → {"error":"Unauthorized"} → close(4001) │ └─ 3. 连接建立后持续推送 {"type":"sys", "system":{cpu,memory,network}, "framework":{...}} ``` ### 前端调用方式 ```javascript // app.js 提供的工具函数 function getCookie(name) { var match = document.cookie.match(new RegExp('(^| )' + name + '=([^;]+)')); return match ? match[2] : ''; } // 仪表盘 WebSocket var tok = getCookie("panel_token"); var ws = new WebSocket("ws://" + location.host + base + "/api/system/ws?token=" + (tok || "")); // 日志 WebSocket var ws = new WebSocket("ws://" + location.host + base + "/api/logs/ws?token=" + (tok || "")); ``` ### WebSocket 端点 | 端点 | 鉴权方式 | 用途 | |------|---------|------| | `/SenSu/api/system/ws?token=` | query param | 系统监控实时推送 | | `/SenSu/api/logs/ws` | Cookie (同源自动带) | 日志实时推送 | --- ## 六、插件路由(双层鉴权) 插件通过 `PluginNetworkBridge.register_http_route()` 注册的路由使用双层鉴权: ``` GET /example_plugin/api/example/info Cookie: panel_token=f93f18546742b6c4... ``` ### 鉴权流程 (`_check_plugin_auth()`) ``` request │ ├─ 第1层: 用户身份验证 │ ├─ 从 Cookie 取 panel_token │ ├─ (或 Authorization: Bearer xxx header) │ ├─ 查 PANEL_SESSION_STORE │ └─ 失败 → 403 {"reason": "未认证"} │ ├─ 第2层: 插件权限检查 │ ├─ 查 permission_service │ ├─ 插件是否有 plugin.network.access ? │ └─ 失败 → 403 {"reason": "插件没有网络访问权限"} │ └─ handler(request) → 响应 ``` ### 插件命令 REST 端点(自动暴露) ``` POST /example_plugin/api/plugin/echo Content-Type: application/json {"args": ["hello", "world"]} ``` 每个 `@plugin_command` 方法自动生成 REST 端点: - 路径: `POST /api/plugin/{command_name}` - 请求体: `{"args": [...], "kwargs": {...}}` - 响应: `{"ok": true, "result": "..."}` --- ## 七、公开端点(无需认证) | 端点 | 说明 | 安全措施 | |------|------|---------| | `GET /health` | 健康检查 | 仅返回 `{"status":"healthy"}` | | `POST /SenSu/api/login` | 登录 | 频率限制 + 哈希比较 | --- ## 八、安全响应头 所有响应自动添加: | Header | 值 | |--------|-----| | `X-Content-Type-Options` | `nosniff` | | `X-Frame-Options` | `DENY` | | `X-XSS-Protection` | `1; mode=block` | | `Referrer-Policy` | `strict-origin-when-cross-origin` | --- ## 九、错误响应格式 ### 认证失败 ```json {"error": "未认证或会话已过期", "status": 401} ``` ### CSRF 失败 ```json {"error": "CSRF 验证失败", "status": 403} ``` ### 频率限制 ```json {"success": false, "msg": "尝试次数过多,请 60 秒后重试"} ``` HTTP 状态码: `429` ### 服务器错误(脱敏) ```json {"error": "Internal server error"} ``` 详细信息仅写入日志,不返回客户端。 --- ## 十、会话持久化 - 登录时 session 写入 `sensu.db` → `config_kv` 表 - 框架重启时从 DB 恢复: `auth.py:_load_sessions_from_db()` - 登出时从 DB 清除 - Token 有效期: Cookie `max-age=259200` (3 天)