简介
本技术文档围绕本项目中的“无状态、不透明随机令牌”认证机制展开,重点说明:
- 令牌的签发、解析、过期与吊销流程
- 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 <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 <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
- 对敏感操作增加二次校验(如短信验证码)