ff693465f6
- README: 新功能列表、start.sh、架构图 - API_Auth_Flow: 新增 API Key 认证章节 - Security_Hardening_Plan: 标记全部完成 - Plugin_Dev_Guide: 新增插件导入安装章节 Co-Authored-By: Claude <noreply@anthropic.com>
8.1 KiB
8.1 KiB
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 |
同上 |
响应
{"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":{...}}
前端调用方式
// 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 |
登录 | 频率限制 + 哈希比较 |
十一、API Key 认证 (服务器间通信)
创建 API Key
在 WebUI "框架设置" → "API Key 管理" 中创建,支持三种权限模板:
| 模板 | 权限 |
|---|---|
readonly |
framework.status.read, plugin.info.read |
monitor |
+ framework.event.subscribe |
full |
admin (全部) |
每个 Key 可选 TTL 过期时间,格式 sk- + 48 hex chars。
使用方式
GET /SenSu/api/system
Authorization: Bearer sk-xxxxxxxx...
验证流程
panel_auth() 拦截
├─ Cookie panel_token → Session Store
├─ 失败 → API Key Store (validate_api_key)
├─ 检查过期时间
└─ 注入 key 自身权限范围
八、安全响应头
所有响应自动添加:
| Header | 值 |
|---|---|
X-Content-Type-Options |
nosniff |
X-Frame-Options |
DENY |
X-XSS-Protection |
1; mode=block |
Referrer-Policy |
strict-origin-when-cross-origin |
九、错误响应格式
认证失败
{"error": "未认证或会话已过期", "status": 401}
CSRF 失败
{"error": "CSRF 验证失败", "status": 403}
频率限制
{"success": false, "msg": "尝试次数过多,请 60 秒后重试"}
HTTP 状态码: 429
服务器错误(脱敏)
{"error": "Internal server error"}
详细信息仅写入日志,不返回客户端。
十、会话持久化
- 登录时 session 写入
sensu.db→config_kv表 - 框架重启时从 DB 恢复:
auth.py:_load_sessions_from_db() - 登出时从 DB 清除
- Token 有效期: Cookie
max-age=259200(3 天)