Files
SenSu/docs/API_Auth_Flow.md
qinglong ff693465f6 docs: README + API_Auth + Security_Plan + Plugin_Guide 更新至 v0.7.0
- README: 新功能列表、start.sh、架构图
- API_Auth_Flow: 新增 API Key 认证章节
- Security_Hardening_Plan: 标记全部完成
- Plugin_Dev_Guide: 新增插件导入安装章节

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-13 20:56:50 +08:00

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 生成 tokensecrets.token_hex(16) → 32 字符 auth.py:handle_login()
4 存入内存PANEL_SESSION_STORE[token] = {username, perms, login_time} 同上
5 持久化到 DBsensu_db.config_kv['panel_sessions'] JSON auth.py:_save_sessions_to_db()
6 设置 Cookiepanel_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.dbconfig_kv
  • 框架重启时从 DB 恢复: auth.py:_load_sessions_from_db()
  • 登出时从 DB 清除
  • Token 有效期: Cookie max-age=259200 (3 天)