文档目录
JWT令牌管理

简介

本技术文档围绕本项目中的“无状态、不透明随机令牌”认证机制展开,重点说明:

  • 令牌的签发、解析、过期与吊销流程
  • Bearer令牌在HTTP请求头中的传递方式与中间件抽取逻辑
  • 鉴权策略与路由级控制
  • 令牌生命周期管理与并发安全
  • 配置项、安全建议与常见问题排查
  • 与第三方认证服务集成的思路与示例路径

需要特别说明:当前实现并未使用标准JWT(JSON Web Token),而是采用“不透明随机token + 服务端存储哈希”的会话式凭证。其优势在于可撤销、可细粒度吊销;代价是每次验证需访问存储层。若未来需要引入JWT,可在现有Guard与中间件体系上扩展新的Guard实现。

项目结构

与令牌认证相关的关键位置如下:

  • API端中间件:负责从请求头提取Bearer token并交由Guard解析
  • Guard(API):封装令牌校验、用户上下文构建与注入
  • 令牌服务:负责令牌签发、解析、吊销与心跳刷新
  • 鉴权策略:根据路由段与配置决定public/optional/required及是否需要工作端身份
  • 登录服务:在IS_API分支签发不透明token并返回给客户端
  • 安全配置:可信代理、Host白名单、响应头、限流与会话硬化
graph TB
Client["客户端"] --> MW["API中间件<br/>UserAuthMiddleware"]
MW --> Guard["API Guard<br/>auth('api')"]
Guard --> TokenSvc["ApiTokenService"]
Guard --> DB["数据库 user / user_token"]
MW --> Policy["UserAuthPolicy<br/>鉴权策略"]
Policy --> Config["配置 auth_modes / work_required"]

核心组件

  • API中间件(UserAuthMiddleware):在HTTP边界读取Authorization: Bearer &lt;token>,调用Guard解析并注入身份
  • API Guard(Auth):校验token有效性、构建用户上下文、注入到当前请求上下文
  • 令牌服务(ApiTokenService):签发不透明随机token、按哈希查找、过期清理、心跳刷新、吊销
  • 鉴权策略(UserAuthPolicy):基于路由段与配置表决策public/optional/required以及是否要求work身份
  • 登录服务(UserAuthService):在IS_API分支签发token并返回给客户端

架构总览

下图展示了API请求的完整鉴权链路:客户端携带Bearer token进入中间件,中间件通过Guard解析token并注入用户上下文,随后由业务控制器处理。

sequenceDiagram
participant C as "客户端"
participant M as "API中间件"
participant G as "API Guard"
participant T as "ApiTokenService"
participant D as "数据库"
C->>M : "Authorization : Bearer <token>"
M->>G : "resolveUserContext(token)"
G->>T : "resolve(token)"
T->>D : "按token_hash查询user_token"
D-->>T : "行数据或空"
T->>T : "过期检查/心跳刷新"
T-->>G : "userId(有效则>0)"
G->>D : "查询用户资料与工作端信息"
D-->>G : "用户/工作端数据"
G-->>M : "上下文(ok, userId, profile, work)"
M->>M : "inject(context) 注入身份"
M-->>C : "继续处理/拒绝(401/403)"

详细组件分析

API中间件(UserAuthMiddleware)

职责

  • 从请求头提取Bearer token
  • 调用Guard解析用户上下文
  • 将上下文注入当前请求
  • 根据策略拒绝未登录或缺少work身份

关键点

  • 使用request()->bearerToken()获取token
  • 通过auth('api')->resolveUserContext()统一解析
  • 拒绝时直接返回401/403 JSON响应并终止
flowchart TD
Start(["进入中间件"]) --> Read["读取 Authorization: Bearer"]
Read --> Resolve["调用 Guard.resolveUserContext(token)"]
Resolve --> Ok{"解析成功?"}
Ok -- 否 --> Reject401["返回 401 未登录"]
Ok -- 是 --> Inject["注入用户上下文"]
Inject --> WorkReq{"需要工作端身份?"}
WorkReq -- 是且缺失 --> Reject403["返回 403 无工作端权限"]
WorkReq -- 否或已有 --> Next["放行至下游"]

API Guard(Auth)

职责

  • 校验token有效性
  • 构建轻量用户资料与工作端信息
  • 提供hydrate注入能力供中间件使用

关键点

  • checkLoginState(token):校验token并返回msg/user_id等
  • resolveUserContext(token):统一解析并组装上下文
  • hydrate(context):将上下文写入当前Guard实例,供后续id()/user()/work()等接口使用
classDiagram
class Auth {
- int userId
- array userProfile
- int workId
- array workRow
+ id() int
+ user() array
+ check() bool
+ guest() bool
+ workId() int
+ work() array
+ hydrate(context) void
+ reset() void
+ checkLoginState(token) array
+ resolveUserContext(token) array
}

令牌服务(ApiTokenService)

职责

  • 签发不透明随机token(64位十六进制,256-bit熵)
  • 仅存token的sha256哈希,明文仅在签发时返回一次
  • 解析token:正则校验、哈希定位、恒定时间比较、过期清理、心跳刷新
  • 吊销:单设备登出或全量吊销

关键点

  • TTL默认30天
  • 解析失败或过期立即删除对应行
  • last_active_at每60秒内不重复写,降低写放大
flowchart TD
Issue(["issue(userId, ip)"]) --> CleanExp["清理过期行"]
CleanExp --> Gen["生成随机token"]
Gen --> Store["插入user_token(含token_hash, expires_at, last_active_at, client_ip)"]
Store --> Return["返回明文token(仅此一次)"]
Resolve(["resolve(token)"]) --> Validate["正则校验"]
Validate --> Hash["计算sha256(token)"]
Hash --> Find["按token_hash查询"]
Find --> ExpCheck{"是否过期?"}
ExpCheck -- 是 --> Delete["删除该行"] --> Fail["返回0"]
ExpCheck -- 否 --> Heartbeat{"last_active间隔>60s?"}
Heartbeat -- 是 --> Update["更新last_active_at"]
Heartbeat -- 否 --> Skip["跳过更新"]
Update --> Success["返回userId"]
Skip --> Success

鉴权策略(UserAuthPolicy)

职责

  • 根据模块/动作/子段/父段与配置表,决策鉴权模式(public/optional/required)
  • 判断该路由是否需要工作端身份

关键点

  • 候选键优先级:精确 > 父段 > 模块根
  • 未在配置中声明的路由默认optional(尝试解析但不拦截)
flowchart TD
In["模块/动作/子段/父段"] --> Build["构建候选键列表"]
Build --> Match["匹配auth_modes配置"]
Match --> Mode{"mode=public|optional|required"}
Match2["匹配work_required配置"] --> Work{"workRequired=true?"}
Mode --> Out["输出(mode, workRequired)"]
Work --> Out

登录服务(UserAuthService)

职责

  • IS_API分支:调用ApiTokenService签发token并返回{user_id, token, field}
  • 前台session路径:设置session壳与CSRF静态token
  • 记住我:生成并下发Cookie token(非API场景)

关键点

  • API登录返回明文token,客户端保存并在后续请求以Bearer形式回传

依赖关系分析

  • 中间件依赖Guard与策略
  • Guard依赖令牌服务与数据库
  • 令牌服务依赖数据库与工具函数
  • 登录服务在IS_API分支依赖令牌服务
graph LR
MW["API中间件"] --> G["API Guard"]
G --> T["ApiTokenService"]
G --> DB["数据库"]
MW --> P["UserAuthPolicy"]
P --> Cfg["配置表"]
Login["UserAuthService(IS_API)"] --> T

性能与并发

  • 令牌解析为O(1)哈希查找,配合索引可快速命中
  • 心跳刷新限制为60秒一次,避免频繁写库
  • 过期行在解析时惰性清理,减少后台任务压力
  • 多设备并存:同一用户可持有多个有效token,互不影响
  • 并发安全:基于数据库唯一约束与原子更新保证一致性(具体索引与约束由DB层保障)

故障排查指南

常见现象与定位要点

  • 401未登录
    • 检查请求头是否包含Authorization: Bearer &lt;token>
    • 确认token格式为64位十六进制
    • 查看user_token表中是否存在对应token_hash且未过期
  • 403无工作端权限
    • 检查路由策略是否要求work身份
    • 确认用户是否具备工作端角色
  • 解析失败但token存在
    • 检查hash_equals比较结果(应恒定时序)
    • 检查expires_at字段是否已过期
  • 心跳未更新
    • 确认last_active_at与当前时间差是否超过60秒

结论

本项目采用“不透明随机token + 服务端哈希存储”的认证方案,具备可撤销、可细粒度吊销、易于审计等优势。通过中间件与Guard的分层设计,实现了清晰的鉴权流程与可扩展性。若未来需要引入JWT,可在现有Guard体系上新增JWT Guard,复用中间件与策略机制。

附录:配置与安全最佳实践

  • 可信代理与Host白名单
    • 在安全配置中设置trusted_proxies与trusted_hosts,防止伪造IP与Host
  • 基线安全响应头
    • 启用X-Content-Type-Options、Referrer-Policy、Permissions-Policy
    • HSTS仅在HTTPS且显式开启时下发
  • 会话Cookie硬化
    • httponly=true,secure跟随IS_HTTPS,SameSite=Lax/Strict
  • 限流与防暴力破解
    • 结合throttle配置与登录失败计数、锁定时间进行防护
  • 令牌安全建议
    • 始终通过HTTPS传输Bearer token
    • 客户端妥善存储token,避免日志泄露
    • 定期轮换密钥与清理过期token
    • 对敏感操作增加二次校验(如短信验证码)
添加日期:2026-10-05