Files
Genarrative/docs/technical/PLATFORM_AUTH_JWT_ADAPTER_DESIGN_2026-04-21.md
kdletters cbc27bad4a
Some checks failed
CI / verify (push) Has been cancelled
init with react+axum+spacetimedb
2026-04-26 18:06:23 +08:00

5.9 KiB
Raw Blame History

platform-auth JWT 适配设计

日期:2026-04-21

1. 文档目的

这份文档用于指导 platform-auth 首个真实能力落地,目标是完成:

  1. platform-auth crate 的 JWT claims 结构。
  2. access token 的签发与校验适配。
  3. api-server 的最小 Bearer JWT 校验入口。

这一步只解决“Axum 自身能稳定签发并校验 JWT”的基础问题不提前把 refresh cookie、短信和微信 OAuth 一起耦合进来。

2. 当前落地范围

本阶段只包含以下实现:

  1. JwtConfig
  2. AccessTokenClaimsInput
  3. AccessTokenClaims
  4. sign_access_token(...)
  5. verify_access_token(...)
  6. api-server 的 Bearer 鉴权中间件
  7. /_internal/auth/claims 内部调试路由

本阶段明确不包含:

  1. refresh cookie 的生成、解析、轮换和吊销。
  2. module-auth / SpacetimeDB 真相表读取 token_version 并做在线比对。
  3. 短信 provider 与微信 OAuth 平台适配。
  4. SpacetimeDB 模块对 Axum 签发 JWT 的消费代码。

3. 设计输入

本实现直接受以下文档约束:

  1. OIDC_JWT_CLAIMS_DESIGN_2026-04-21.md
  2. SPACETIMEDB_AXUM_OSS_BACKEND_REWRITE_DESIGN_2026-04-20.md

关键冻结点:

  1. iss/sub/sid/provider/roles/ver/phone_verified/binding_status 是当前 access token 的固定字段。
  2. sub 是稳定 user_id
  3. sid 是会话 ID不是单次 token ID。
  4. roles 当前至少包含 user
  5. 不允许把手机号、openid、风控状态、refresh token hash 放进 JWT。

4. crate 边界

4.1 platform-auth

负责:

  1. 组织 JWT 结构。
  2. 执行签名与验签。
  3. 执行基础 claims 完整性校验。

不负责:

  1. 用户是否存在。
  2. token_version 是否仍是数据库最新值。
  3. refresh session 是否已被吊销。
  4. provider 之外的业务规则判断。

4.2 api-server

负责:

  1. Authorization 头提取 Bearer token。
  2. 调用 platform-auth 校验。
  3. 把已校验 claims 写入请求上下文。
  4. 在本阶段提供最小内部验收入口 /_internal/auth/claims

不负责:

  1. 自己再实现一套 JWT 编解码逻辑。
  2. 把 claims 结构拆散成多个重复 helper。

5. 配置口径

当前阶段 api-server 读取并传入 platform-auth 的配置如下:

配置项 环境变量 默认值 说明
issuer GENARRATIVE_JWT_ISSUER https://auth.genarrative.local OIDC 风格发行者标识。
secret GENARRATIVE_JWT_SECRET genarrative-dev-secret 当前阶段沿用对称签名密钥。
access token TTL GENARRATIVE_JWT_ACCESS_TOKEN_TTL_SECONDS 7200 access token 有效期,单位秒。

兼容回退:

  1. issuer 可回退读取 JWT_ISSUER
  2. secret 可回退读取 JWT_SECRET
  3. TTL 可回退读取 JWT_EXPIRES_IN,支持:
    • 纯秒值,例如 900
    • s/m/h/d 后缀,例如 30m2h

6. 算法选择

当前阶段固定采用:

  1. alg = HS256

理由:

  1. 与当前 Node 基线兼容,迁移阻力最低。
  2. 先把 claims、配置口径和 Bearer 主链稳定下来。
  3. 若未来升级到非对称签名,应作为独立任务处理,而不是夹带进当前重写链路。

7. Rust 结构设计

7.1 AccessTokenClaimsInput

用途:

  1. 作为业务层输入。
  2. 不承载 iat/exp/iss 这种平台计算字段。

字段:

  1. user_id
  2. session_id
  3. provider
  4. roles
  5. token_version
  6. phone_verified
  7. binding_status
  8. display_name

7.2 AccessTokenClaims

用途:

  1. 对应最终 JWT payload。
  2. 直接用于签名与验签结果输出。

字段:

  1. iss
  2. sub
  3. sid
  4. provider
  5. roles
  6. ver
  7. phone_verified
  8. binding_status
  9. display_name
  10. iat
  11. exp

8. 校验规则

8.1 签发前校验

  1. issuersecret 不能为空。
  2. TTL 必须大于 0
  3. sub 不能为空。
  4. sid 不能为空。
  5. roles 不能为空数组。
  6. exp 必须晚于 iat

8.2 验签时校验

  1. 算法必须是 HS256
  2. 签名必须正确。
  3. iss 必须匹配当前配置。
  4. exp/iat/iss/sub 必须存在。
  5. 反序列化后的 sid/provider/roles/ver/phone_verified/binding_status 必须完整。

当前阶段不做的校验:

  1. ver 与数据库最新 token version 比对。
  2. sidrefresh_session 活跃状态比对。
  3. roles 的细粒度授权判断。

9. api-server 最小接线

本阶段 api-server 接线规则如下:

  1. AppConfig 增加 JWT 相关配置。
  2. AppState 在启动时构造唯一一份 JwtConfig
  3. require_bearer_auth 中间件从请求头读取 Bearer token。
  4. 验签成功后把 claims 以 AuthenticatedAccessToken 写入 request extensions。
  5. 内部路由 /_internal/auth/claims 用于返回当前已校验 claims作为阶段验收与调试入口。

说明:

  1. 这个内部路由不是最终对外 contract。
  2. 它的存在是为了在 module-auth 与正式 /api/auth/me 落地前,先把 Bearer 主链单独跑通。

10. 测试策略

当前阶段要求至少覆盖:

  1. platform-auth 的 JWT 签发与验签回环。
  2. issuer 不匹配时拒绝。
  3. 空角色拒绝。
  4. api-server 在无 Bearer token 时返回 401
  5. api-server 在合法 Bearer token 下返回 claims。

11. 完成定义

当以下条件满足时,本任务视为完成:

  1. Rust workspace 中存在真实可编译的 platform-auth crate。
  2. api-server 已能使用 platform-auth 校验 Bearer JWT。
  3. 工作区测试与编译可通过。
  4. 任务清单已同步更新。

12. 后续衔接

下一阶段继续衔接:

  1. refresh cookie 读取与轮换。
  2. module-auth 会话真相与 token_version 在线校验。
  3. /api/auth/me/api/auth/refresh 等正式接口。
  4. SpacetimeDB 对 Axum JWT 的身份透传验证。