简介
本技术文档围绕“用户认证中间件”展开,覆盖以下关键主题:
- JWT/令牌验证流程:令牌签发、存储、校验与过期处理(本项目采用无状态 API Token,非 JWT)。
- 会话管理策略:前台/后台基于 Session 的登录态恢复、Cookie 硬化、持久化后端。
- 权限检查系统:后台模块级 RBAC、API 端按模块/动作的鉴权模式配置与工作端身份叠加。
- 认证失败处理:统一错误码、响应格式与跳转策略。
- 配置与安全最佳实践:可信代理、Host 白名单、安全响应头、限流与会话 Cookie 策略。
- 第三方集成:微信小程序等 SNS 登录接入要点。
项目结构
认证相关代码主要分布在三个入口域:
- API 端:无状态 Token 认证,中间件从请求头解析 Bearer Token,交由 Guard 解析并注入上下文。
- 前台 Web:基于 Session 的登录态恢复,中间件根据路由策略决定是否要求登录。
- 后台管理:基于 Session 的管理员登录态恢复 + 模块级权限判定。
graph TB
Client["客户端"] --> API["API 中间件<br/>UserAuthMiddleware"]
Client --> Front["前台中间件<br/>UserAuthMiddleware"]
Client --> Admin["后台中间件<br/>AuthMiddleware"]
API --> AuthAPI["API Guard<br/>auth('api')"]
Front --> AuthFront["前台 Guard<br/>auth('front')"]
Admin --> AuthAdmin["后台 Guard<br/>auth('admin')"]
AuthAPI --> TokenSvc["ApiTokenService"]
AuthFront --> Session["Session 存储"]
AuthAdmin --> Session
Admin --> Perm["PermissionMiddleware"]
Perm --> Gate["AdminGate"]
核心组件
- API 认证中间件:负责从请求头提取 Bearer Token,调用 Guard 解析登录态;未通过返回 401 JSON,工作端受限接口额外校验 work 身份返回 403。
- 前台认证中间件:根据路由策略决定是否要求登录;拒绝时重定向到登录页或返回带跳转地址的 401 JSON(XHR)。
- 后台认证与权限中间件:先恢复管理员 Session,再判定模块访问权限(含子资源别名映射与自编辑放行)。
- API Token 服务:签发不透明随机 token(仅一次明文返回),服务端仅存哈希;支持过期清理、单设备吊销与全量吊销。
- 安全配置:可信代理、Host 白名单、安全响应头、限流、会话 Cookie 硬化。
架构总览
下图展示 API 端一次受保护请求的完整调用链:中间件提取 Token → Guard 解析 → Token 服务校验 → 注入上下文 → 控制器执行。
sequenceDiagram
participant C as "客户端"
participant M as "API 中间件"
participant G as "API Guard(auth('api'))"
participant T as "ApiTokenService"
participant S as "业务控制器"
C->>M : "HTTP 请求 (Authorization : Bearer <token>)"
M->>G : "resolveUserContext(token)"
G->>T : "resolve(token)"
T-->>G : "userId 或 0"
alt "已登录"
G-->>M : "上下文 {ok : true, userId, ...}"
M->>S : "继续管道"
S-->>C : "业务响应"
else "未登录/无效"
G-->>M : "{ok : false}"
M-->>C : "401 UNAUTHORIZED(JSON)"
end
详细组件分析
API 端认证中间件(UserAuthMiddleware)
- 职责边界:从请求头取 Bearer Token,交给 Guard 解析;根据鉴权模式配置决定 required/optional/public;work_required 子策略叠加工作端校验。
- 拒绝策略:未登录返回 401 JSON;无工作端权限返回 403 JSON。
- 配置来源:api/init/middleware.php 中的 auth_modes 与 work_required。
flowchart TD
Start(["进入 API 中间件"]) --> ReadCfg["读取鉴权模式配置"]
ReadCfg --> Mode{"模式"}
Mode --> |public| Skip["跳过登录态解析"]
Mode --> |optional| Try["尝试解析登录态"]
Mode --> |required| Must["必须登录"]
Try --> Ok{"解析成功?"}
Must --> Ok
Ok --> |是| Inject["注入上下文"]
Ok --> |否| Reject["返回 401 JSON"]
Inject --> WorkCheck{"是否 work_required?"}
WorkCheck --> |是| HasWork{"有工作端身份?"}
HasWork --> |否| Forbid["返回 403 JSON"]
HasWork --> |是| Next["放行至控制器"]
WorkCheck --> |否| Next
Next --> End(["结束"])
Reject --> End
Forbid --> End
前台认证中间件(UserAuthMiddleware)
- 职责边界:使用 auth('front') 解析登录态;根据路由策略决定是否要求登录;拒绝时重定向到登录页或返回带 jump_url 的 401 JSON(XHR)。
- 配置来源:front/init/middleware.php 中的 auth_modes。
后台认证与权限中间件
- 认证中间件:从 Session 恢复管理员身份,未登录重定向到登录页。
- 权限中间件:在认证通过后,依据当前模块/动作与管理员 action_list 判定访问;超级管理员直接放行;manager 模块允许本人编辑/更新。
- 子资源别名:部分子资源继承父模块权限,避免新增子资源导致越权拦截。
classDiagram
class PermissionMiddleware {
+handle(next)
}
class AdminGate {
+canAccess(admin, cur, action, targetId) bool
+isManagerSelfEdit(admin, cur, action, targetId) bool
-subModuleAliases : array
}
PermissionMiddleware --> AdminGate : "调用"
API Token 服务(ApiTokenService)
- 令牌签发:生成高熵随机 token,仅一次明文返回;服务端仅保存 sha256(token_hash),防止泄露可逆。
- 令牌解析:正则校验格式 → 哈希查找 → 恒定时间比较 → 过期清理 → 心跳刷新 last_active_at。
- 吊销机制:支持单设备吊销与按用户全量吊销(改密/封禁/退出所有设备)。
flowchart TD
Issue["issue(userId, ip)"] --> CleanExp["清理该用户过期 token"]
CleanExp --> Gen["生成随机 token"]
Gen --> Store["写入 user_token(哈希+过期时间+IP)"]
Store --> Return["返回明文 token(仅此一次)"]
Resolve["resolve(token)"] --> Validate["格式校验"]
Validate --> Hash["计算哈希并查找"]
Hash --> Found{"命中?"}
Found --> |否| Fail["返回 0"]
Found --> |是| Expire{"是否过期?"}
Expire --> |是| Delete["删除过期行"] --> Fail
Expire --> |否| Heartbeat["心跳刷新 last_active_at"] --> Success["返回 userId"]
小程序前端认证状态管理
- 本地存储:登录后将 api_token、user_id、loginEd 写入本地存储;登出时清除。
- 状态恢复:应用启动时读取本地存储,并通过接口拉取用户信息以同步 is_login/is_vip/is_work/is_distribution 标志。
- 登录态维护:当接口返回 UNAUTHORIZED 时清空登录态;其他网络错误保留当前登录态,避免弱网误判。
依赖关系分析
- API 中间件依赖 Guard 与 ApiTokenService;Guard 依赖数据库表 user_token。
- 前台/后台中间件依赖各自 Guard 与 Session 存储。
- 后台权限判定依赖 AdminGate 与管理员 action_list。
- 安全配置影响 Request IP/Host 判定与 Session Cookie 行为。
graph LR
API_MW["API 中间件"] --> Guard_API["API Guard"]
Guard_API --> TokenSvc["ApiTokenService"]
Front_MW["前台中间件"] --> Guard_Front["前台 Guard"]
Admin_MW["后台中间件"] --> Guard_Admin["后台 Guard"]
Admin_MW --> PermMW["权限中间件"]
PermMW --> Gate["AdminGate"]
Config["安全配置"] --> Request["Request IP/Host"]
Config --> Session["Session Cookie"]
性能与并发
- API Token 解析:
- 哈希查找 O(1) 平均复杂度;恒定时间比较防时序攻击。
- 心跳刷新 last_active_at 限制为每 60 秒一次,降低写放大。
- 签发时清理过期行,避免表膨胀。
- 会话存储:
- 默认文件存储,可通过自定义 Store 实现 Redis/DB 等后端以提升并发能力。
- 建议在高并发场景启用共享存储(如 Redis)并合理设置 session.gc_maxlifetime。
- 限流:
- 安全配置中 throttle.store 指定限流存储路径;敏感端点可在中间件中按需限速。
故障排查指南
- 常见错误码与含义:
- UNAUTHORIZED:未携带或无效凭证,API 返回 401 JSON;前台 XHR 返回 401 并附带 jump_url。
- FORBIDDEN:已登录但无权限或无工作端身份,API 返回 403 JSON;前台重定向到用户中心。
- NOT_FOUND:资源不存在。
- RATE_LIMITED:请求过频被限流。
- 排查步骤:
- 检查 API 请求是否携带 Authorization: Bearer <token>,且 token 格式为 64 位十六进制。
- 确认 token 未过期;若过期,重新登录获取新 token。
- 检查工作端接口是否命中 work_required 配置,并确保用户具备工作端身份。
- 前台登录失败时查看是否触发账户锁定或验证码校验失败。
- 后台 403:确认管理员类型是否为 defined,action_list 是否包含当前模块;子资源需登记别名。
- 日志与审计:
- 登录成功/失败均记录审计日志,便于定位问题。
结论
本项目采用“前台/后台基于 Session、API 基于无状态 Token”的分层认证架构:
- API 端通过 ApiTokenService 实现高安全性的令牌签发与校验,支持多设备并存与细粒度吊销。
- 前台/后台通过中间件与 Guard 组合,结合路由级鉴权模式配置,实现灵活的访问控制。
- 后台权限模型基于模块/动作白名单与子资源别名映射,兼顾灵活性与安全性。
- 安全配置提供可信代理、Host 白名单、安全响应头与会话 Cookie 硬化,满足生产环境基线要求。
- 小程序前端通过本地存储与接口同步维持登录态,并在 UNAUTHORIZED 时幂等清理状态。
附录
- 令牌有效期:ApiTokenService::TTL 默认 30 天,可按需调整。
- 会话 Cookie 策略:httponly=true,secure=null(跟随 IS_HTTPS),samesite=Lax,use_strict_mode=true。
- 限流存储:throttle.store 指向 STORAGE_PATH . 'cache/throttle/',可按需迁移至外部存储。
- 第三方集成要点:
- 微信小程序登录:控制器在登录成功后调用 UserAuthService::login() 签发 API Token,并返回 dou.auth 标志供前端同步。
- 其他 SNS 登录:遵循统一登录流程,校验凭据后由调用方决定写 Session 或签发 Token。