简介
本文件面向开发者,系统化说明 DouPHP 前台中间件管道的执行顺序与作用机制,覆盖内置中间件(CSRF 保护、用户认证、请求限流、安全头部设置)的功能与配置方式,并提供自定义中间件的创建方法、典型示例、调试与排错建议。目标是帮助你在不侵入业务逻辑的前提下,以横切关注点的方式扩展前台请求处理流程。
项目结构
前台中间件位于 front/middleware,包含 CSRF、用户认证、限流与安全头四个关键中间件;鉴权模式与策略在 front/init/middleware.php 中集中声明;通用管道与注册能力由 core/foundation/middleware 提供;前台路由调度通过 front/foundation/routing/Router.php 将请求交给中央调度器执行。
graph TB
A["前台入口<br/>Router::dispatch()"] --> B["前端解析器<br/>FrontResolver"]
B --> C["中央调度器<br/>Dispatcher::run()"]
C --> D["中间件注册表<br/>MiddlewareRegistry"]
D --> E["中间件管道<br/>MiddlewarePipeline"]
E --> F["控制器动作"]
subgraph "前台中间件"
M1["CsrfMiddleware"]
M2["UserAuthMiddleware"]
M3["ThrottleMiddleware"]
M4["SecurityHeadersMiddleware"]
end
D --> M1
D --> M2
D --> M3
D --> M4
核心组件
- 中间件管道:按顺序组合多个中间件,最终调用控制器动作。
- 中间件注册表:将“全局默认别名栈”与“路由级细化”组装为最终可执行的中间件实例链,支持跳过、豁免、追加与参数化。
- 前台中间件:
- CSRF 保护:校验表单与特定 GET 链接的令牌,区分一次性令牌与静态令牌。
- 用户认证:基于 auth('front') 解析登录态,按模块/动作的鉴权模式进行 required/optional/public 分支处理,并支持工作端身份子策略。
- 请求限流:对敏感端点按 IP 限制频率,超限返回友好提示并重定向。
- 安全头部:输出基线安全响应头(行为由基类实现)。
架构总览
前台请求进入 Router,解析得到 DispatchPlan 后交由 Dispatcher 执行。Dispatcher 根据 RouteEntry 携带的路由级中间件配置,结合 MiddlewareRegistry 的全局默认别名栈,生成最终的中间件实例链,并通过 MiddlewarePipeline 依次执行,最后到达控制器动作。
sequenceDiagram
participant Client as "客户端"
participant Router as "前台路由器"
participant Resolver as "前端解析器"
participant Dispatcher as "中央调度器"
participant Registry as "中间件注册表"
participant Pipeline as "中间件管道"
participant MW as "中间件链"
participant Ctrl as "控制器动作"
Client->>Router : "HTTP 请求"
Router->>Resolver : "resolve(request)"
Resolver-->>Router : "DispatchPlan"
Router->>Dispatcher : "run(plan, container)"
Dispatcher->>Registry : "compose(defaultAliases, entry)"
Registry-->>Dispatcher : "中间件实例[]"
Dispatcher->>Pipeline : "run(then=控制器动作)"
Pipeline->>MW : "依次调用 handle()"
MW-->>Pipeline : "继续或短路返回"
Pipeline-->>Client : "响应"
详细组件分析
CSRF 保护中间件
- 作用:校验 POST 表单与指定 GET 链接的 CSRF 令牌,防止跨站请求伪造。
- 令牌模型:
- 登录会员共享静态令牌 static_user(用于大多数场景)。
- 匿名表单使用一次性令牌,抗重放。
- 路由映射:
- 一次性令牌路由集合:注册表维护一组 action 到 token id 的映射。
- 带 token 的 GET 路由集合:某些操作即使为 GET 也需校验令牌。
- 拒绝策略:抛出领域异常并引导至首页,语言包缺省时回退到非法访问提示。
flowchart TD
Start(["进入 CsrfMiddleware"]) --> CheckRoute["匹配路由候选<br/>module/sub/action"]
CheckRoute --> OneTime{"是否一次性令牌路由?"}
OneTime -- 是 --> UseOneTime["使用一次性令牌 ID"]
OneTime -- 否 --> UseStatic["使用静态令牌 static_user"]
UseOneTime --> Validate["校验请求中的令牌"]
UseStatic --> Validate
Validate --> Valid{"令牌有效?"}
Valid -- 否 --> Reject["抛出领域异常<br/>提示并跳转首页"]
Valid -- 是 --> Next["放行至下一中间件/控制器"]
用户认证中间件
- 作用:基于 auth('front') 解析当前会话的用户上下文,按鉴权模式决定是否放行。
- 鉴权模式:
- public:匿名可访问,不尝试恢复登录态。
- optional:尝试恢复登录态,失败不拦截(公共浏览等)。
- required:必须登录。
- work_required:required 的子策略,命中后额外校验工作端身份。
- 拒绝策略:
- 未认证:XHR 请求返回 JSON 401 并附带跳转地址;普通请求重定向到登录页并保留原地址。
- 禁止访问:重定向到用户中心。
sequenceDiagram
participant Client as "客户端"
participant AuthMW as "UserAuthMiddleware"
participant Guard as "auth('front')"
participant Policy as "鉴权策略"
participant Controller as "控制器"
Client->>AuthMW : "请求"
AuthMW->>Guard : "resolveUserContext()"
Guard-->>AuthMW : "用户上下文"
AuthMW->>Policy : "根据 module/action 判定模式"
alt 需要登录且未登录
AuthMW-->>Client : "重定向到登录页 / JSON 401"
else 允许访问
AuthMW->>Controller : "注入上下文并放行"
Controller-->>Client : "响应"
end
请求限流中间件
- 作用:对敏感端点按 IP 进行频率限制,与登录失败限流互补独立。
- 限流规则:针对验证码、登录、注册、手机登录、找回密码、短信验证、公共表单提交、聊天相关端点等设定窗口与最大次数。
- 拒绝策略:设置 Retry-After 响应头,抛出领域异常并提示后跳转首页。
flowchart TD
S(["进入 ThrottleMiddleware"]) --> Match["匹配路由候选"]
Match --> Found{"找到限流规则?"}
Found -- 否 --> Pass["不限流,直接放行"]
Found -- 是 --> Check["检查 IP 在窗口内请求数"]
Check --> Over{"超过配额?"}
Over -- 是 --> Reject["设置 Retry-After<br/>抛出异常并跳转首页"]
Over -- 否 --> Pass
安全头部中间件
- 作用:输出基线安全响应头(如 X-Frame-Options、X-Content-Type-Options 等),具体行为由基类实现。
- 特点:薄壳中间件,无需额外配置即可生效。
鉴权模式配置
- 位置:front/init/middleware.php
- 内容:
- auth_modes:按 module/module/action 维度声明 public/optional/required/work_required。
- work_required:需要工作端身份的模块列表。
- 约定:新增需登录或需匿名豁免的前台模块时,必须在 auth_modes 中显式登记,避免依赖默认语义承载安全边界。
依赖关系分析
- 前台路由调度:Router 负责解析请求并交给 Dispatcher 执行。
- 中间件注册表:根据默认别名栈与路由级细化(skipAllMiddleware、withoutMiddleware、middleware、middlewareParams)组装最终中间件实例链。
- 中间件管道:按顺序执行中间件,最终调用控制器动作。
- 各中间件依赖:
- UserAuthMiddleware 依赖 auth('front') 门面与语言包。
- CsrfMiddleware 依赖 CSRF 工具与语言包。
- ThrottleMiddleware 依赖限流基础设施与语言包。
- SecurityHeadersMiddleware 依赖安全头基类。
graph LR
R["Router"] --> D["Dispatcher"]
D --> Reg["MiddlewareRegistry"]
Reg --> Pipe["MiddlewarePipeline"]
Pipe --> Ctl["控制器动作"]
Reg --> |实例化| M1["CsrfMiddleware"]
Reg --> |实例化| M2["UserAuthMiddleware"]
Reg --> |实例化| M3["ThrottleMiddleware"]
Reg --> |实例化| M4["SecurityHeadersMiddleware"]
性能考量
- 中间件数量与复杂度:每个中间件都会增加一次函数调用与可能的 I/O 开销(如读取配置、查询缓存)。应仅启用必要的中间件,并将昂贵操作延迟或异步化。
- 限流中间件:对高频接口开启限流有助于保护后端资源,但需注意窗口大小与阈值调优,避免误伤正常流量。
- CSRF 校验:仅在必要路由上校验令牌,减少不必要的计算与存储访问。
- 安全头部:几乎零开销,建议始终启用。
- 鉴权模式:尽量将公共页面设为 optional/public,减少不必要的会话恢复与权限判断。
故障排除指南
- CSRF 校验失败:
- 现象:页面停留过久或重新登录后提交表单被拒。
- 排查:确认模板已正确渲染 CSRF 令牌;检查一次性令牌路由是否正确映射;确认 GET 链接是否属于带 token 的白名单。
- 参考路径:CsrfMiddleware 拒绝逻辑:92-95、一次性令牌路由映射:44-71、GET 令牌路由:76-83。
- 用户认证失败:
- 现象:跳转到登录页或返回 401 JSON。
- 排查:检查模块/动作是否在 auth_modes 中登记为 required;确认 session 是否有效;XHR 请求是否携带 from=js。
- 参考路径:UserAuthMiddleware 拒绝逻辑:77-97、鉴权模式配置:18-31。
- 请求被限流:
- 现象:收到 Retry-After 提示并被重定向。
- 排查:确认目标路由是否在限流规则中;调整窗口与最大次数;检查 IP 是否被误判。
- 参考路径:限流规则定义:32-50、拒绝逻辑:75-83。
- 安全头部缺失:
- 现象:浏览器安全警告。
- 排查:确认 SecurityHeadersMiddleware 已加入默认栈;检查基类实现是否被覆盖。
- 参考路径:SecurityHeadersMiddleware:23-26。
结论
DouPHP 前台中间件管道通过统一的注册与执行机制,将 CSRF、认证、限流与安全头等横切关注点解耦于业务控制器之外。借助鉴权模式配置与路由级细化,开发者可以灵活控制不同模块与动作的安全策略。遵循本文档的最佳实践与排错指引,可在保证安全与稳定的前提下,高效扩展前台请求处理能力。
附录
如何创建自定义中间件
- 步骤概览:
- 新建类实现中间件接口(继承框架提供的抽象基类或实现统一接口)。
- 在 handle 方法中实现前置/后置逻辑,必要时短路返回响应。
- 若需参数化,实现 ParameterizedMiddleware 并在 setRouteParameters 中解析参数。
- 在对应端的 Resolver 中注册别名映射,使 MiddlewareRegistry 能实例化该类。
- 在全局默认栈或路由级配置中使用该别名,完成装配。
- 注意事项:
- 安全相关中间件(如 CSRF、认证)构造期异常不应被吞掉,确保请求不会绕过安全检查。
- 中间件应尽量幂等、轻量,避免阻塞 I/O。
- 对于高并发场景,优先使用缓存与异步任务。
典型示例(概念性)
- 权限检查:
- 在 handle 中读取当前用户角色与目标资源权限,不满足则抛出禁止访问异常或重定向。
- 日志记录:
- 在进入与退出时记录请求 URI、耗时、状态码,注意脱敏敏感信息。
- 数据预处理:
- 标准化输入参数、填充默认值、转换时间与时区,确保后续控制器获得一致的数据形态。
中间件配置方式
- 全局默认栈:在各端 Resolver 中定义默认别名列表,决定所有请求都经过的基础中间件。
- 路由级细化:
- skipAllMiddleware:跳过全部中间件(最高优先级)。
- withoutMiddleware:从默认栈中移除指定别名。
- middleware:在末尾追加额外中间件(支持 alias:p1,p2 参数 DSL)。
- middlewareParams:为指定别名提供独占参数覆盖。
- 鉴权模式:在 front/init/middleware.php 中按模块/动作声明 public/optional/required/work_required。