简介
本技术文档围绕 DouPHP 的“用户认证中间件系统”展开,重点解释以下能力:
- 会话管理与令牌管理:前台基于 Session + Remember Token;API 端基于不透明随机 token。
- 权限验证与访问控制:通过策略表(auth_modes)决定 public/optional/required,并通过 work_required 叠加工作端身份校验。
- 抽象基类与策略模式:AbstractUserAuthMiddleware 提供模板方法骨架,UserAuthPolicy 负责策略解析。
- 前端与 API 的差异实现:前端重定向到登录页,API 返回 JSON 错误码。
- 多端认证同步与令牌管理最佳实践:前后端各自维护登录态,API 侧通过 token 跨端共享会员身份。
该文档既适合初学者理解“认证中间件的作用”,也为高级开发者提供扩展自定义认证策略、新增模块鉴权配置的实操指引。
项目结构
认证相关代码主要分布在如下位置:
- 核心基类与策略:core/foundation/middleware
- 前端中间件与 Guard:front/middleware, front/facade/Auth.php
- API 中间件与 Guard:api/middleware, api/facade/Auth.php
- 配置:front/init/middleware.php, api/init/middleware.php
- 服务:core/service/user/ApiTokenService.php, core/service/user/UserAuthService.php
graph TB
subgraph "核心"
A["AbstractUserAuthMiddleware<br/>模板方法"]
P["UserAuthPolicy<br/>策略解析"]
S1["UserAuthService<br/>前台会话/Remember"]
S2["ApiTokenService<br/>API Token签发/校验"]
end
subgraph "前端"
F_MW["front/UserAuthMiddleware"]
F_Auth["front/Auth"]
F_CFG["front/init/middleware.php"]
end
subgraph "API"
AP_MW["api/UserAuthMiddleware"]
AP_Auth["api/Auth"]
AP_CFG["api/init/middleware.php"]
end
A --> P
F_MW --> A
AP_MW --> A
F_MW --> F_Auth
AP_MW --> AP_Auth
F_MW --> F_CFG
AP_MW --> AP_CFG
F_Auth --> S1
AP_Auth --> S2
核心组件
- AbstractUserAuthMiddleware:定义统一鉴权流程(读取路由段 → 策略决策 → 三态分支 → 注入上下文 → work 子策略 → 放行),子类仅实现 guard 选择、配置文件路径与拒绝响应。
- UserAuthPolicy:纯解析器,根据 module/action/sub/parent 与配置表生成 mode 与 workRequired。
- 前端 UserAuthMiddleware:走 auth('front'),失败时重定向登录页或返回带跳转信息的 JSON(XHR)。
- API UserAuthMiddleware:从 Authorization: Bearer 取 token,走 auth('api'),失败直接返回 401/403 JSON。
- Guard:
- front/Auth:Session + Remember Token 恢复,构建 userProfile/work。
- api/Auth:基于 ApiTokenService 校验 token,构建轻量级 userProfile/work。
- 服务:
- UserAuthService:前台登录、remember token、登录失败计数与锁定、IP 限流等。
- ApiTokenService:签发不透明 token,清理过期行,存储哈希值。
架构总览
下图展示一次受保护请求在 front 与 api 两端的处理差异:
sequenceDiagram
participant C as "客户端"
participant MW as "认证中间件"
participant POL as "UserAuthPolicy"
participant G as "Guard(auth)"
participant SVC as "服务层"
participant CTRL as "控制器"
C->>MW : 发起请求
MW->>POL : 解析路由段+配置表
POL-->>MW : {mode, workRequired}
alt mode=public
MW-->>CTRL : 直接放行
else mode=optional|required
MW->>G : resolveUserContext()
G->>SVC : 校验会话/token
SVC-->>G : 上下文(ok/user/work)
G-->>MW : 上下文
opt required且未登录
MW-->>C : 前端重定向/JSON 401
end
opt workRequired且无work
MW-->>C : 前端重定向/JSON 403
end
MW-->>CTRL : 注入身份后放行
end
详细组件分析
抽象基类:AbstractUserAuthMiddleware
- 职责:统一鉴权流程(模板方法),加载配置,解析路由段,调用策略,执行三态分支,注入上下文,检查 work 身份,放行或拒绝。
- 关键行为:
- 构造函数加载 auth_modes 与 work_required。
- handle() 中先计算策略,再按 public/optional|required 分流。
- optional 下解析失败仍放行匿名访问。
- required 下解析失败拒绝。
- workRequired 为 true 时额外校验 hasWorkIdentity。
- 扩展点:子类需实现 configFile()/resolveContext()/inject()/hasWorkIdentity()/rejectUnauthenticated()/rejectForbidden()。
flowchart TD
Start(["进入 handle"]) --> ReadRoute["读取路由段(module/action/sub/parent)"]
ReadRoute --> Policy["UserAuthPolicy::resolve()"]
Policy --> Mode{"mode"}
Mode --> |public| NextPublic["直接放行"]
Mode --> |optional|required| Resolve["resolveContext()"]
Resolve --> Ok{"ok?"}
Ok --> |否| Required{"required?"}
Required --> |是| RejectU["rejectUnauthenticated()"]
Required --> |否| NextOpt["放行(匿名)"]
Ok --> |是| Inject["inject(context)"]
Inject --> WorkReq{"workRequired?"}
WorkReq --> |是| HasWork{"hasWorkIdentity()"}
HasWork --> |否| RejectF["rejectForbidden()"]
HasWork --> |是| NextAll["放行"]
WorkReq --> |否| NextAll
策略解析:UserAuthPolicy
- 输入:module、action、sub、parent、auth_modes、work_required。
- 输出:{mode, workRequired}。
- 候选键优先级:精确 module/sub/action > module/sub > module/action > parent/sub/action > parent/sub > module > parent。
- 默认模式:optional(未在配置表中声明的路由默认尝试解析但不拦截)。
flowchart TD
In["输入: module/action/sub/parent"] --> Build["构建候选键列表(细到粗)"]
Build --> MatchMode["匹配 auth_modes 得到 mode"]
Build --> MatchWork["匹配 work_required 得到 workRequired"]
MatchMode --> Out["输出: {mode, workRequired}"]
MatchWork --> Out
前端认证中间件:front/UserAuthMiddleware
- 配置文件:front/init/middleware.php。
- 解析方式:auth('front')->resolveUserContext(),基于 Session + Remember Token。
- 拒绝策略:
- 未登录:优先判断 XHR(from=js),返回带 jump_url 的 JSON 401;否则重定向到登录页并附带 redirect。
- 无 work 身份:重定向到用户中心。
- 注入:auth('front')->hydrate(context)。
sequenceDiagram
participant C as "浏览器"
participant MW as "front/UserAuthMiddleware"
participant G as "front/Auth"
C->>MW : 请求
MW->>G : resolveUserContext()
G-->>MW : {ok,userProfile,work,...}
alt ok=false 且 required
MW-->>C : 401 JSON(含jump_url) 或 重定向登录页
else ok=true 且 workRequired且无work
MW-->>C : 重定向到用户中心
else 通过
MW->>G : hydrate(context)
MW-->>C : 继续控制器
end
API 认证中间件:api/UserAuthMiddleware
- 配置文件:api/init/middleware.php。
- 解析方式:从 Authorization: Bearer 取 token,调用 auth('api')->resolveUserContext(token)。
- 拒绝策略:
- 未登录:返回 401 JSON。
- 无 work 身份:返回 403 JSON。
- 注入:auth('api')->hydrate(context)。
sequenceDiagram
participant App as "小程序/客户端"
participant MW as "api/UserAuthMiddleware"
participant G as "api/Auth"
App->>MW : 请求(Authorization : Bearer <token>)
MW->>G : resolveUserContext(token)
G-->>MW : {ok,userProfile,work,...}
alt ok=false 且 required
MW-->>App : 401 JSON
else ok=true 且 workRequired且无work
MW-->>App : 403 JSON
else 通过
MW->>G : hydrate(context)
MW-->>App : 继续控制器
end
会话与令牌管理
- 前台会话:
- 登录成功后写入 session(user_id, shell, ontime, field) 并生成 CSRF token。
- 支持 remember token:将用户表的 token_hash 与 Cookie 中的明文 token 配对,用于自动恢复登录态。
- 会话校验:matchSession 比对 shell 与数据库用户状态,touchSession 刷新心跳。
- API 令牌:
- 登录成功签发不透明随机 token,服务端只存 hash,明文仅返回一次。
- 每次签发同时清理该用户过期 token 行,避免表膨胀;允许多设备并存。
- 校验时通过 ApiTokenService.resolve(token) 获取 userId,再构造上下文。
classDiagram
class UserAuthService {
+login(user, field, remember)
+token(userId)
+checkLoginState()
+ipRateLimit(ip, limit, window) bool
+isLocked(userId) int
}
class ApiTokenService {
+issue(userId, ip) string
}
class FrontAuth {
+resolveUserContext() array
+hydrate(context) void
+restoreFromToken() bool
}
class ApiAuth {
+resolveUserContext(token) array
+hydrate(context) void
+checkLoginState(token) array
}
FrontAuth --> UserAuthService : "使用"
ApiAuth --> ApiTokenService : "使用"
权限验证与访问控制
- 策略表(auth_modes):对每个模块/动作/子段声明 public/optional/required。
- 工作端策略(work_required):命中后要求当前用户具备有效 work 身份。
- 覆盖规则:更具体的键(如 module/sub/action)优先于模块根。
- 前端与 API 分别维护各自的配置,便于差异化控制。
依赖关系分析
- 中间件依赖策略解析:所有 UserAuthMiddleware 都依赖 UserAuthPolicy 做模式决策。
- 中间件依赖 Guard:前端用 front/Auth,API 用 api/Auth。
- Guard 依赖服务:
- front/Auth 依赖 UserAuthService(会话、remember、限流、锁定)。
- api/Auth 依赖 ApiTokenService(token 签发/校验)。
- 配置驱动:两端通过 init/middleware.php 声明鉴权模式与工作端要求。
graph LR
MW_F["front/UserAuthMiddleware"] --> POL["UserAuthPolicy"]
MW_A["api/UserAuthMiddleware"] --> POL
MW_F --> GA["front/Auth"]
MW_A --> AA["api/Auth"]
GA --> US["UserAuthService"]
AA --> ATS["ApiTokenService"]
MW_F --> CFG_F["front/init/middleware.php"]
MW_A --> CFG_A["api/init/middleware.php"]
性能考虑
- 策略解析为纯函数,时间复杂度与候选键数量线性相关,开销极低。
- 前端鉴权可能触发数据库查询以校验 session/shell 与用户资料,建议:
- 合理设置 remember token,减少频繁重新登录。
- 使用缓存优化用户资料与工作端信息(若业务量大)。
- API 鉴权每次校验 token 哈希,注意:
- 定期清理过期 token 行(ApiTokenService 已内置清理)。
- 在高并发场景可结合 Redis 缓存 token 有效性(可选扩展)。
- 登录失败限流与账号锁定降低暴力破解风险,但会引入额外查询,应配合缓存或本地计数器优化。
故障排查指南
- 未登录被拦截:
- 前端:检查是否携带有效 session/remember cookie;XHR 请求会返回 401 JSON 并包含 jump_url。
- API:检查 Authorization 头是否携带有效 token;确认 token 未过期。
- 无工作端权限:
- 检查当前用户是否具备 work 身份;确认路由命中 work_required。
- 登录失败过多:
- 检查 IP 限流与账号锁定;查看登录失败审计日志。
- 策略配置问题:
- 确认新模块已在对应 init/middleware.php 中显式登记 auth_modes;漏登记会被门禁扫描发现。
结论
DouPHP 的用户认证中间件系统通过抽象基类与策略模式实现了高度一致的鉴权流程,同时在前端与 API 两端提供了符合各自形态的拒绝策略与身份解析方式。通过配置化的 auth_modes 与 work_required,可以灵活控制模块级与动作级的访问级别与工作端权限。结合前台 Session/Remember Token 与 API 的不透明 token,系统在安全性、可扩展性与性能之间取得了良好平衡。
附录:配置与使用示例
- 白名单配置(公开接口):
- 在对应 init/middleware.php 中将模块/动作设置为 public,例如 user/login、user/register 等。
- 可选增强(登录后增强体验):
- 将模块设置为 optional,允许匿名访问并在登录后注入身份以提升展示。
- 必须登录:
- 将模块设置为 required,未登录将被拦截。
- 工作端权限:
- 在 work_required 中添加模块/动作,命中后要求具备 work 身份。
- 重定向规则(前端):
- 未登录时重定向到登录页并附带 redirect;XHR 请求返回带 jump_url 的 JSON。
- 错误处理(API):
- 未登录返回 401 JSON;无 work 身份返回 403 JSON。
- 多端认证同步与令牌管理最佳实践:
- 前台使用 Session + Remember Token 保持长期登录。
- API 使用不透明 token,服务端仅存哈希;客户端在 Authorization 头回传。
- 登录成功后签发 token 并清理过期行;必要时在服务端增加 token 黑名单或吊销机制。
- 多设备并存时,允许同一用户存在多个有效 token;如需强制下线,可设计吊销逻辑。