简介
本技术文档聚焦 DouPHP 的用户认证策略系统,围绕 UserAuthPolicy 的策略模式实现展开,解释路由级鉴权决策算法、模块/动作/子模块的权限匹配规则;详解 auth_modes 配置表的工作原理与 public、optional、required 三种鉴权模式的判断逻辑和执行流程;记录 work_required 工作身份验证机制,说明如何区分普通用户与工作身份访问控制;提供策略配置的完整示例(模块级、动作级、子模块级);详细说明路由参数覆盖机制,展示如何在路由定义中动态设置鉴权模式;最后给出策略扩展指南,帮助初学者理解策略模式在认证中的应用,并为高级开发者提供复杂权限场景的解决方案。
项目结构
DouPHP 的前台与 API 端采用“中间件 + 策略”的鉴权模型:
- 策略层:UserAuthPolicy 负责根据路由段与配置表计算鉴权模式与是否要求工作身份。
- 中间件层:AbstractUserAuthMiddleware 提供模板方法,统一处理解析上下文、三态分支、注入身份、work 校验与放行;前端与 API 分别实现具体 Guard 选择与拒绝响应。
- 配置层:各端 init/middleware.php 维护 auth_modes 与 work_required 配置表。
- 路由层:声明式路由将 URL 映射到控制器/动作,并可通过可参数化中间件进行路由级覆盖。
graph TB
subgraph "请求进入"
R["路由解析<br/>module/sub/action"]
end
subgraph "鉴权中间件"
AUM["AbstractUserAuthMiddleware<br/>handle()"]
UAP["UserAuthPolicy::resolve()"]
FWM["Front UserAuthMiddleware"]
AWI["Api UserAuthMiddleware"]
end
subgraph "配置"
FM["front/init/middleware.php"]
AM["api/init/middleware.php"]
end
R --> AUM --> UAP
AUM --> |读取配置| FM
AUM --> |读取配置| AM
AUM --> |前端实现| FWM
AUM --> |API 实现| AWI
核心组件
- UserAuthPolicy:纯解析器,输入为 module/sub/action/parent 与配置表,输出鉴权模式与是否要求工作身份。候选键按“精确 > 父段 > 模块根”优先级匹配,默认模式为 optional。
- AbstractUserAuthMiddleware:模板方法封装 handle(),完成路由段提取、策略决策、上下文解析、身份注入、work 校验与拒绝响应。
- Front UserAuthMiddleware:使用 front guard 解析与会话恢复,拒绝时跳转登录页或返回 JSON 401(XHR)。
- Api UserAuthMiddleware:从 Authorization 头取 token,调用 api guard 解析,拒绝时直接返回 JSON 错误。
- 配置表:auth_modes 定义模块/动作/子模块的鉴权模式;work_required 定义需要工作身份的候选键集合。
架构总览
下图展示了从路由解析到鉴权执行的端到端流程,包括策略决策、上下文解析、身份注入与拒绝路径。
sequenceDiagram
participant Client as "客户端"
participant Router as "路由解析"
participant MW as "鉴权中间件"
participant Policy as "UserAuthPolicy"
participant Guard as "Guard(front/api)"
participant Ctrl as "控制器"
Client->>Router : 请求 /?route=...
Router-->>MW : 命中路由(module/sub/action)
MW->>Policy : resolve(module,action,sub,parent,authModes,workRequired)
Policy-->>MW : {mode, workRequired}
alt mode == public
MW-->>Ctrl : 直接放行
else mode == optional|required
MW->>Guard : resolveUserContext(token?)
Guard-->>MW : {ok, context}
alt ok == false
alt mode == required
MW-->>Client : 拒绝(401/跳转)
else mode == optional
MW-->>Ctrl : 匿名放行
end
else ok == true
MW->>Guard : hydrate(context)
alt workRequired == true && 无工作身份
MW-->>Client : 拒绝(403)
else
MW-->>Ctrl : 放行
end
end
end
详细组件分析
策略类 UserAuthPolicy
- 职责:仅做策略解析,不读 Request/Auth,所有输入显式入参,符合安全与可测试性原则。
- 决策算法:
- 构建候选键列表:module/sub/action、module/sub、module/action、parent/sub/action、parent/sub、module、parent,去重后按顺序匹配。
- 匹配 auth_modes 中的值,若为 public/optional/required 则采用该模式;未命中则回退到默认模式 optional。
- 同时检查 work_required 是否包含任一候选键,决定是否需要工作身份。
- 复杂度:候选键数量固定且较小,时间复杂度 O(1),空间复杂度 O(1)。
flowchart TD
Start(["开始"]) --> Build["构建候选键列表"]
Build --> MatchMode{"匹配 auth_modes?"}
MatchMode --> |是| SetMode["设置模式(public/optional/required)"]
MatchMode --> |否| UseDefault["使用默认模式(optional)"]
SetMode --> CheckWork{"候选键是否在 work_required?"}
UseDefault --> CheckWork
CheckWork --> |是| WorkTrue["workRequired=true"]
CheckWork --> |否| WorkFalse["workRequired=false"]
WorkTrue --> End(["结束"])
WorkFalse --> End
中间件基类 AbstractUserAuthMiddleware
- 职责:封装鉴权流程模板方法 handle(),统一执行:
- 提取路由段(module/sub/action/parent),其中 parent 用于下划线分隔的复合模块名。
- 调用 UserAuthPolicy::resolve 获取模式与 workRequired。
- 支持路由级覆盖:若 setRouteParameters 注入了合法模式,则优先使用该模式。
- 解析上下文(resolveContext)、注入身份(inject)、校验工作身份(hasWorkIdentity)、拒绝处理(rejectUnauthenticated/rejectForbidden)。
- 配置加载:构造时加载对应端配置文件,提取 auth_modes 与 work_required。
classDiagram
class AbstractUserAuthMiddleware {
-array authModes
-array workRequired
-string|null routeModeOverride
+setRouteParameters(params) void
+handle(next) mixed
#configFile() string
#resolveContext() array
#inject(context) void
#hasWorkIdentity() bool
#rejectUnauthenticated() void
#rejectForbidden() void
}
class UserAuthPolicy {
+resolve(module, action, sub, parent, authModes, workRequired) array
}
AbstractUserAuthMiddleware --> UserAuthPolicy : "调用策略决策"
前端鉴权中间件 Front UserAuthMiddleware
- 配置路径:FRONT_PATH . 'init/middleware.php'。
- 上下文解析:通过 auth('front')->resolveUserContext() 解析会话登录态。
- 身份注入:auth('front')->hydrate(context) 写入当前会员缓存。
- 工作身份:workId() > 0 表示具备工作身份。
- 拒绝行为:
- 未登录:XHR(from=js) 返回 JSON 401 并附带跳转地址;否则重定向到登录页并携带 redirect 参数。
- 无工作身份:重定向到会员中心。
API 鉴权中间件 Api UserAuthMiddleware
- 配置路径:API_PATH . 'init/middleware.php'。
- 上下文解析:从 Authorization 头取 Bearer token,调用 auth('api')->resolveUserContext(token)。
- 身份注入:auth('api')->hydrate(context)。
- 工作身份:workId() > 0。
- 拒绝行为:
- 未登录:返回 JSON 401。
- 无工作身份:返回 JSON 403。
配置表 auth_modes 与 work_required
- 作用域:前端与 API 各自维护一份配置表,分别位于 front/init/middleware.php 与 api/init/middleware.php。
- 键模式:支持 module、module/action、module/sub、module/sub/action 四种粒度,越精确优先级越高。
- 模式值:
- public:匿名直达,不尝试解析登录态。
- optional:尝试解析登录态,失败不拦截(默认模式)。
- required:必须登录,否则拒绝。
- work_required:命中后在 required 基础上额外校验工作身份,未命中则不强制。
路由参数覆盖机制
- 接口:ParameterizedMiddleware 允许中间件接收路由级参数。
- 机制:当路由声明了 user_auth:public|optional|required 等别名参数时,框架为该路由新建中间件实例并调用 setRouteParameters,注入的模式会覆盖配置表决策。
- 适用场景:对个别动作临时放宽或收紧鉴权,无需修改全局配置。
策略配置示例
- 模块级:例如将某个模块整体设为 required,确保所有动作需登录。
- 动作级:针对特定动作设置为 public 或 optional,如登录注册入口公开。
- 子模块级:对 module/sub 或 module/sub/action 进行更细粒度的覆盖,如 chat/list 对游客开放浏览。
- 工作身份:在 work_required 中添加模块或子模块键,使这些路由在 required 基础上额外校验工作身份。
路由定义与策略联动
- 前台路由:user 模块的公开动作(登录、注册、找回密码等)在配置表中登记为 public,其余 user/* 默认 required。
- API 路由:user 模块的公开接口(登录注册、检查登录状态等)登记为 public;部分子控制器(如 weixin)保留显式 name 段,module 归一为父 user + sub。
- 组合:路由解析产出 module/sub/action,交由中间件策略决策,最终决定是否放行或拒绝。
依赖关系分析
- 中间件依赖策略:AbstractUserAuthMiddleware 依赖 UserAuthPolicy 进行模式决策。
- 两端中间件差异:前端与 API 中间件继承同一基类,差异在于 Guard 选择、上下文解析与拒绝响应。
- 配置依赖:两端中间件各自加载对应端配置文件,互不影响。
- 路由依赖:路由解析产出 module/sub/action,供中间件使用;可参数化中间件支持路由级覆盖。
graph LR
Policy["UserAuthPolicy"] --> MWBase["AbstractUserAuthMiddleware"]
MWBase --> FrontMW["Front UserAuthMiddleware"]
MWBase --> ApiMW["Api UserAuthMiddleware"]
FrontMW --> FrontCfg["front/init/middleware.php"]
ApiMW --> ApiCfg["api/init/middleware.php"]
Route["路由解析"] --> MWBase
性能考量
- 策略解析开销极低:候选键数量有限,匹配过程为常数时间与常数空间。
- 配置加载仅在中间件构造时执行一次,避免重复 IO。
- 建议:保持 auth_modes 配置精简明确,避免过多细粒度条目导致维护成本上升;对高频路由尽量使用模块级默认模式,减少覆盖项。
故障排查指南
- 现象:页面被重定向到登录页或返回 401/403。
- 检查 auth_modes 是否正确登记该模块/动作/子模块的模式。
- 确认是否命中 work_required 且当前用户不具备工作身份。
- 对于 XHR 请求,检查 from=js 参数以区分跳转与 JSON 返回。
- 现象:API 返回 401/403。
- 检查 Authorization 头是否携带有效 token。
- 确认 API 中间件配置中对应接口的模式是否为 required。
- 如需工作身份,确认 work_required 是否包含该路由候选键。
- 现象:路由级覆盖未生效。
- 确认路由声明了 user_auth 参数且值为 public/optional/required。
- 检查中间件是否实现了 ParameterizedMiddleware 接口并被框架正确实例化。
结论
DouPHP 的用户认证策略系统通过 UserAuthPolicy 与 AbstractUserAuthMiddleware 的组合,实现了清晰、可扩展的路由级鉴权模型。配置表 auth_modes 与 work_required 提供了灵活的权限控制能力,支持模块/动作/子模块的多粒度覆盖;路由参数覆盖机制进一步增强了动态调整的能力。前端与 API 端通过各自中间件实现差异化行为,既保证了统一性,又兼顾了端特性。遵循本文的配置与扩展指南,可在保证安全边界的前提下,高效地管理复杂权限场景。
附录
- 策略模式在认证中的应用:将鉴权决策从业务逻辑中解耦,集中到策略类,便于测试与维护。
- 扩展新鉴权模式:
- 在 UserAuthPolicy 中扩展候选键匹配与模式枚举。
- 在中间件中增加新的拒绝或放行分支。
- 在配置表中登记新模式的使用范围。
- 自定义权限检查逻辑:
- 在 hasWorkIdentity 或 resolveContext 中接入自定义服务。
- 结合 work_required 实现“登录后+工作身份”的双重校验。