Skip to content

Api Authentication

worable edited this page Oct 20, 2026 · 2 revisions

认证方式

StackPanel 有四种认证通道:

通道 凭证 适用
服务端会话 Authorization: Bearer <token> 或 Cookie Web BFF 与后台
平台 API Token Authorization: Bearer sp_... 开放平台 /api/v1、/mcp
OAuth 第三方登录 提供方授权码 用户登录
渠道签名 Ed25519 请求签名头 /sp/v1/* 渠道伙伴

服务端会话(Session)

会话是不透明随机串(前缀 sess_),状态存于 Redis,多副本共享、可即时吊销。 请求头或 Cookie 均可携带:

curl -H "Authorization: Bearer <token>" http://127.0.0.1:3001/auth/me

获取 Token

登录/注册由内置 login 插件提供(POST /login、POST /register):

POST /login
{ "email": "admin@stackpanel.local", "password": "NewPass123456" }

→ {
  "token": "sess_...",
  "user": { "id": "...", "email": "...", "role": "ADMIN", "status": "ACTIVE" }
}

Token 说明

  • 形态:sess_ + 32 字节随机串,不是 JWT,服务端不解析载荷。
  • 存储:Redis sp:session:<token>(JSON:userId / createdAt / lastSeenAt), 另有 sp:session:user:<userId> 集合用于批量吊销。
  • 有效期:SESSION_TTL_SECONDS(默认 43200 = 12 小时),访问时滑动续期, 但最多每 60 秒写一次。
  • 可吊销:登出、改密、封禁会立即在各副本生效(区别于旧的无状态 JWT)。
  • 兼容:旧版 HS256 JWT 仍被接受,并在首次访问时平滑迁移为服务端会话 (apps/api/src/plugins/auth.ts 的 extractSession)。

Cookie

登录成功后,API 同时设置 HttpOnly Cookie(默认名 sp_session):

Set-Cookie: sp_session=<token>; HttpOnly; SameSite=Lax; Path=/; Max-Age=43200

Cookie 与 Bearer 使用同一会话 token。extractSession 先取 Bearer 头,其次取 Cookie (token = bearer ?? cookie)。

BFF 模式下,Web 的 src/proxy.ts(Next 16 替代 middleware)只做 /admin 边缘鉴权; 真正从 Cookie 取 token 并在转发到 API 时放入 Bearer 头的是 apps/web/src/lib/api.ts 的 getAuthedApiClient()(经 getSessionToken())。

Cookie 安全

属性 值 说明
httpOnly true 防止 JS 读取
sameSite lax 防止 CSRF
secure API_COOKIE_SECURE HTTPS 后应设为 true
path / 全站有效

OAuth 第三方登录

流程

1. GET /auth/oauth/providers          → 获取可用提供者
2. GET /auth/oauth/:id/authorize      → 获取授权 URL(重定向用户)
3. POST /auth/oauth/:id/callback      → 交换凭证,获取 token

获取提供者列表

GET /auth/oauth/providers
→ { "providers": [{ "id": "wowid", "name": "WowID" }] }

获取授权 URL

GET /auth/oauth/wowid/authorize?state=random_state&codeChallenge=xxx&nonce=xxx
→ { "authorizeUrl": "https://account.wowcraft.cn/oauth2/authorize?...", "state": "random_state" }

回调

POST /auth/oauth/wowid/callback
{
  "code": "authorization_code",
  "state": "random_state",
  "codeVerifier": "PKCE_verifier",
  "nonce": "nonce_value"
}

→ { "token": "...", "user": { ... } }

身份绑定

  • 通过 providerId + externalId 查 UserIdentity
  • 已存在 → 匹配本地用户
  • 不存在 → 检查 email 冲突 → 无冲突则创建用户(无密码)+ 身份绑定

登出

POST /logout
→ 204(吊销会话并清除 Cookie)

会话验证流程

apps/api/src/plugins/auth.ts 的 extractSession:

请求 → extractSession:
  1. 取 Bearer 头,其次 Cookie
  2. 若像平台 API Token(默认 sp_ 前缀)→ 解析 ApiToken:
       effectivePermissions = token.scopes ∩ 用户当前权限
  3. 否则查服务端会话(Redis sp:session:<token>):
       命中 → 查 DB user,检查 status === 'ACTIVE',注入 request.user
  4. 否则若是旧 JWT(三段式)→ verifySession 后迁移为服务端会话

用户被禁用(status: DISABLED)后,已有会话在下次访问时立即失效(会话被丢弃)。

平台 API Token(开放平台)

自服务端点(需登录会话):

方法 路径 说明
GET /me/api-tokens 列出本人 Token(不返回明文)
GET /me/api-tokens/scopes 可授予的 scope 列表
POST /me/api-tokens 创建;明文仅返回一次
PATCH /me/api-tokens/:id 改名 / 改 scope / 启停 / 改过期
DELETE /me/api-tokens/:id 吊销
GET /me/api-tokens/:id/usage 本人该 Token 的调用审计
  • 明文前缀默认 sp_(STACKPANEL_API_TOKEN_PREFIX 可改),库内只存哈希。
  • scope 必须 ⊆ 持有者当前权限;effectivePermissions 是 token scope 与用户权限的交集, 因此用户降权会立即收窄其所有 Token。
  • 支持 IP 白名单、过期时间;每 Token RPM(OPEN_API_TOKEN_RPM,默认 240)与并发上限 (OPEN_API_TOKEN_CONCURRENCY,默认关闭)在共享状态中计数。
  • 管理端另有 /admin/api-tokens、/admin/api-tokens/:id/usage 用于审计。

渠道签名(SP v1)

/sp/v1/* 使用 Ed25519 请求签名,签名覆盖方法、路径、时间戳、nonce 与原始请求体; nonce 单次有效,超出签名窗口或重放返回 401,每渠道 RPM 超限返回 429。 写操作(POST)另需 Idempotency-Key。完整协议见 StackPanel Partner API v1。

当前用户

GET /auth/me
→ { "user": { "id": "...", "email": "...", "role": "ADMIN", "status": "ACTIVE", "lastLoginAt": "...", "createdAt": "...", "updatedAt": "..." } }

轻量会话探针(永不 401,供 Web BFF / proxy 首层门禁使用):

GET /auth/session
→ { "user": { "id": "...", "role": "ADMIN" } }   # 未登录时 { "user": null }

已绑定身份

GET /auth/identities
→ { "identities": [{ "id": "...", "providerId": "wowid", "email": "...", "displayName": "...", "createdAt": "..." }] }

Clone this wiki locally