简介
本文件面向前台中间件系统,系统性说明中间件的注册与执行顺序、全局与路由级中间件的区别,以及内置中间件(安全头、限流、用户认证、CSRF)的工作原理与配置方式。文档还涵盖请求进入时的预处理与响应返回时的后处理模型、自定义中间件开发方法、性能优化建议、调试技巧,以及组合使用的最佳实践与案例。
项目结构
前台中间件位于 front/middleware 目录,每个中间件继承 core/foundation/middleware 下的抽象基类,实现端侧差异化行为;鉴权策略配置集中在 front/init/middleware.php;路由风格与匹配规则在 config/route.php;管道串联由 core/foundation/middleware/MiddlewarePipeline.php 负责。
graph TB
subgraph "前台中间件"
A["SecurityHeadersMiddleware"]
B["ThrottleMiddleware"]
C["UserAuthMiddleware"]
D["CsrfMiddleware"]
end
subgraph "基础能力"
E["AbstractSecurityHeadersMiddleware"]
F["AbstractThrottleMiddleware"]
G["AbstractUserAuthMiddleware"]
H["AbstractCsrfMiddleware"]
I["MiddlewarePipeline"]
end
A --> E
B --> F
C --> G
D --> H
I --> A
I --> B
I --> C
I --> D
核心组件
- SecurityHeadersMiddleware:为命中路由的响应设置基线安全头(如 X-Content-Type-Options、X-Frame-Options、Referrer-Policy、Permissions-Policy),并在 HTTPS 且开启时下发 HSTS。
- ThrottleMiddleware:对敏感端点按 IP 进行定向限流,未配额路由直接放行。
- UserAuthMiddleware:基于路由段与策略表决定 public/optional/required,解析并注入登录态,必要时校验工作端身份。
- CsrfMiddleware:对改写型方法与特定 GET 链接进行 CSRF 令牌校验,支持一次性令牌与静态令牌两种模式。
架构总览
中间件以“管道”形式串联,请求进入时依次经过安全头、限流、认证、CSRF 等前置处理;控制器处理后,响应沿原路返回,完成后置处理(如安全头已在最前置下发)。
sequenceDiagram
participant Client as "客户端"
participant Pipeline as "中间件管道"
participant Sec as "安全头"
participant Thr as "限流"
participant Auth as "用户认证"
participant Csr as "CSRF"
participant Ctrl as "控制器"
Client->>Pipeline : "HTTP 请求"
Pipeline->>Sec : "handle(next)"
Sec-->>Pipeline : "设置安全头并继续"
Pipeline->>Thr : "handle(next)"
Thr-->>Pipeline : "通过或拒绝(限流)"
Pipeline->>Auth : "handle(next)"
Auth-->>Pipeline : "通过/拒绝(跳转或JSON)"
Pipeline->>Csr : "handle(next)"
Csr-->>Pipeline : "通过或拒绝(CSRF错误)"
Pipeline->>Ctrl : "调用控制器"
Ctrl-->>Pipeline : "响应对象"
Pipeline-->>Client : "返回响应"
详细组件分析
安全头中间件(SecurityHeadersMiddleware)
- 职责:为命中路由的响应设置基线安全头;HTTPS 且配置开启时下发 HSTS。
- 关键点:仅在 headers_sent() 前设置;仅覆盖已匹配路由;三端薄壳子类共享同一行为。
- 配置项:security.headers(content_type_options、frame_options、referrer_policy、permissions_policy、hsts)。
flowchart TD
Start(["进入安全头中间件"]) --> ReadCfg["读取 security.headers"]
ReadCfg --> SetHdr{"headers 已发送?"}
SetHdr --> |是| Next["跳过设置,继续管道"]
SetHdr --> |否| Apply["根据配置设置安全头"]
Apply --> HSTS{"是否HTTPS且启用HSTS?"}
HSTS --> |是| AddHSTS["设置 Strict-Transport-Security"]
HSTS --> |否| Next
AddHSTS --> Next
Next --> End(["返回 next() 继续后续中间件"])
限流中间件(ThrottleMiddleware)
- 职责:对敏感端点按 IP 进行定向限流;未配额路由直接放行。
- 关键逻辑:
- 候选键:module/sub/action → module/sub → module/action → module。
- 超限:设置 Retry-After 并抛出领域异常,渲染统一错误页。
- 存储:使用 ThrottleStore(默认文件缓存目录)。
- 典型配额:验证码、登录/注册/找回密码、公共表单提交、聊天流等。
flowchart TD
S(["进入限流中间件"]) --> Extract["提取 module/action/sub/ip"]
Extract --> Limit{"是否配置配额?"}
Limit --> |否| Pass["直接放行"]
Limit --> |是| Check["store.tooMany(key,max,window)?"]
Check --> |是| Reject["reject(retryAfter)"]
Check --> |否| Hit["store.hit(key,window)"]
Hit --> Pass
Reject --> End(["结束"])
Pass --> End
用户认证中间件(UserAuthMiddleware)
- 职责:依据路由段与策略表决定访问模式(public/optional/required),解析并注入登录态,必要时校验工作端身份。
- 关键点:
- 配置文件:front/init/middleware.php 中的 auth_modes 与 work_required。
- 上下文解析:通过 auth('front') 获取用户上下文并注入。
- 拒绝策略:XHR 返回 JSON 401 + jump_url;普通请求重定向到登录页。
- 执行流程:public 直达;optional 尝试登录态但不拦截;required 必须登录否则拒绝。
sequenceDiagram
participant MW as "UserAuthMiddleware"
participant Policy as "UserAuthPolicy"
participant Auth as "auth('front')"
participant Next as "下游中间件/控制器"
MW->>MW : "读取策略配置"
MW->>Policy : "resolve(module,action,sub,parent)"
Policy-->>MW : "{mode,workRequired}"
alt mode == public
MW->>Next : "直接放行"
else mode == optional|required
MW->>Auth : "resolveUserContext()"
Auth-->>MW : "{ok,...}"
alt ok == false
opt required
MW-->>MW : "rejectUnauthenticated()"
end
opt optional
MW->>Next : "匿名放行"
end
else ok == true
MW->>Auth : "hydrate(context)"
opt workRequired && 无工作身份
MW-->>MW : "rejectForbidden()"
end
MW->>Next : "放行"
end
end
CSRF 中间件(CsrfMiddleware)
- 职责:对改写型方法与特定 GET 链接进行 CSRF 令牌校验;支持一次性令牌与静态令牌。
- 关键点:
- 豁免名单:外部回调等无需令牌的接口可声明式豁免。
- GET-token 路由:对带 token 的幂等 GET 链接也进行校验。
- 令牌选择:一次性令牌用于匿名表单;其余走 static_user。
- 拒绝策略:提示页面过期并重定向首页。
flowchart TD
S(["进入CSRF中间件"]) --> Build["构建候选键列表"]
Build --> Except{"是否在豁免名单?"}
Except --> |是| Pass["直接放行"]
Except --> |否| Method{"是否为改写方法?"}
Method --> |是| Verify["读取token并verify/check"]
Method --> |否| GetToken{"是否GET-token路由?"}
GetToken --> |否| Pass
GetToken --> |是| Verify
Verify --> Ok{"校验通过?"}
Ok --> |是| Pass
Ok --> |否| Reject["reject() -> 提示并重定向"]
Pass --> End(["继续管道"])
Reject --> End
依赖关系分析
- 中间件均实现统一的 MiddlewareInterface,并通过 handle($next) 串接。
- 各中间件依赖的基础设施:
- 安全头:Config::get('security.headers')、request()->isSecure()。
- 限流:ThrottleStore(默认文件存储)、Config::get('security.throttle.store')。
- 认证:auth('front') Guard、UserAuthPolicy、前端策略配置。
- CSRF:csrf() 助手、Request::csrfToken()、Request::isAjax()。
- 路由风格影响模块/动作/子段的解析,从而影响中间件决策键。
graph LR
R["路由解析(config/route.php)"] --> M["中间件管道(MiddlewarePipeline)"]
M --> SH["安全头(AbstractSecurityHeaders)"]
M --> TL["限流(AbstractThrottle)"]
M --> UA["认证(AbstractUserAuth)"]
M --> CS["CSRF(AbstractCsrf)"]
SH --> CFG["security.headers"]
TL --> STORE["ThrottleStore"]
UA --> AUTH["auth('front') / UserAuthPolicy"]
CS --> CSRF["csrf() / Request"]
性能考虑
- 安全头:仅在 headers_sent() 前设置一次,避免重复开销;HSTS 仅在 HTTPS 且开启时设置。
- 限流:未配额路由直接放行;限流键为 module.action.ip,减少不必要的计数;超限快速失败。
- 认证:public 直达不解析登录态;optional 仅尝试恢复登录态;required 才强制校验。
- CSRF:AJAX 预检使用 check() 不消费一次性令牌,避免二次提交冲突;非 AJAX 使用 verify() 防重放。
- 管道顺序:将轻量且高收益的中间件(安全头、限流)置于前面,尽早短路失败请求。
故障排查指南
- 安全头未生效
- 检查 security.headers 配置是否正确;确认响应头尚未发送;确认当前请求为 HTTPS(若需 HSTS)。
- 参考路径:core/foundation/middleware/AbstractSecurityHeadersMiddleware.php:41-83
- 限流误伤
- 核对路由是否命中配额;检查候选键生成是否符合预期;查看 ThrottleStore 目录是否存在写入权限。
- 参考路径:core/foundation/middleware/AbstractThrottleMiddleware.php:60-126、front/middleware/ThrottleMiddleware.php:37-83
- 认证跳转循环
- 检查 auth_modes 中对应模块/动作的模式;确认 XHR from=js 场景下返回 JSON 401 与 jump_url;确认登录页 redirect 参数正确。
- 参考路径:front/middleware/UserAuthMiddleware.php:77-99、front/init/middleware.php:32-126
- CSRF 校验失败
- 确认表单是否包含 csrf()->token();AJAX 是否携带 X-CSRF-Token/X-XSRF-Token;外部回调是否已声明豁免;GET-token 路由是否列入 getTokenRoutes。
- 参考路径:core/foundation/middleware/AbstractCsrfMiddleware.php:59-116、front/middleware/CsrfMiddleware.php:62-95
结论
前台中间件系统通过统一的抽象基类与管道模型,实现了安全头、限流、认证与 CSRF 的可插拔组合。通过策略配置与路由级参数,既能满足全局一致性,又能灵活适配不同模块的安全与性能需求。遵循本文的最佳实践与排障指引,可在保证安全性的同时获得良好的用户体验与系统性能。
附录
注册机制与执行顺序
- 全局中间件:由管道统一装配,所有命中路由都会经过。
- 路由级中间件:可通过 setRouteParameters 注入参数(如限流次数/窗口、鉴权模式覆盖),实现细粒度控制。
- 执行顺序建议:安全头 → 限流 → 认证 → CSRF → 控制器。
配置选项速查
- 安全头:security.headers(content_type_options、frame_options、referrer_policy、permissions_policy、hsts.enabled/max_age/subdomains)。
- 限流:security.throttle.store(存储目录);各路由配额在 ThrottleMiddleware::$limits。
- 认证:front/init/middleware.php 的 auth_modes(module/module/action → public/optional/required)与 work_required。
- CSRF:getTokenRoutes 列出需校验 GET 的路由;except 列出完全豁免的路由。
自定义中间件开发方法
- 新建中间件类实现 MiddlewareInterface 或继承相应抽象基类。
- 在 handle($next) 中实现前置/后置逻辑,必要时抛出异常或返回响应以中断管道。
- 如需路由级参数,实现 ParameterizedMiddleware 的 setRouteParameters。
- 将中间件加入管道或在路由定义中按需挂载。
组合使用案例与最佳实践
- 公开浏览页:仅启用安全头与限流(可选),认证设为 optional,CSRF 不触发。
- 会员私有页:安全头 + 限流 + 认证 required + CSRF(POST/PUT/PATCH/DELETE)。
- 敏感操作(登录/注册/验证码):安全头 + 严格限流 + CSRF 一次性令牌 + optional/required 认证策略。
- 外部回调:在 CSRF except 中声明豁免,避免误拒。