文档目录
前台认证中间件

简介

本技术文档聚焦 DouPHP 前台认证中间件,围绕 FrontendUserAuthMiddleware(即 front/middleware/UserAuthMiddleware)对 AbstractUserAuthMiddleware 的具体实现展开,系统说明:

  • 前台会话管理、用户状态解析与重定向处理机制
  • 登录状态验证流程:Cookie/session 检查、用户信息提取与身份注入
  • 前台白名单配置:哪些页面允许匿名访问(注册、登录、商品浏览等)
  • 未登录时的重定向逻辑:跳转登录页、返回 URL 保存与错误提示
  • 配置选项与使用示例:自定义登录页、重定向规则、错误处理
  • 前台与 API 端认证差异及拒绝响应方式
  • 多端会话同步最佳实践:Web 端与小程序端一致性

项目结构

前台认证由“策略 + 基类 + 前端实现”三层构成:

  • 策略层:UserAuthPolicy 根据路由段与配置表决定鉴权模式(public/optional/required)以及是否需要工作端身份。
  • 基类层:AbstractUserAuthMiddleware 提供模板方法,统一处理请求解析、策略决策、三态分支、身份注入与工作端校验。
  • 前端实现层:UserAuthMiddleware 指定前台 guard(auth('front'))、配置文件路径与拒绝响应(重定向或 JSON)。
graph TB
A["请求进入"] --> B["AbstractUserAuthMiddleware.handle()"]
B --> C["UserAuthPolicy.resolve()"]
C --> D{"mode: public/optional/required"}
D --> |public| E["直接放行"]
D --> |optional/required| F["resolveContext() -> auth('front')->resolveUserContext()"]
F --> G{"是否已登录"}
G --> |否 & required| H["rejectUnauthenticated() 重定向/JSON"]
G --> |是| I["inject() -> auth('front')->hydrate()"]
I --> J{"work_required?"}
J --> |是 且无工作端| K["rejectForbidden() 重定向到用户中心"]
J --> |否| L["放行到控制器"]

核心组件

  • UserAuthMiddleware(前台实现)
    • 指定配置文件路径为前台中间件配置
    • 通过 auth('front') 解析上下文并注入身份缓存
    • 未登录时:XHR from=js 返回 JSON 401 并附带 jump_url;普通页面重定向到登录页并携带 redirect 参数
    • 无工作端权限时:重定向到用户中心
  • AbstractUserAuthMiddleware(通用骨架)
    • 加载前台鉴权配置(auth_modes、work_required)
    • 基于路由段与策略表决策 mode 与 workRequired
    • 三态分支:public 直达;optional 失败放行;required 失败拒绝
    • 注入身份后按 work_required 决定是否要求工作端身份
  • UserAuthPolicy(策略解析器)
    • 构建候选键(精确 > 父段 > 模块根),从配置表中匹配最细粒度模式
    • 默认模式 optional,确保新增模块默认尝试恢复登录态但不拦截
  • Auth Guard(front/facade/Auth.php)
    • resolveUserContext:优先从 Session 恢复,否则尝试 remember token
    • verifySession/matchSession:校验 session shell 与数据库一致
    • hydrate:将上下文写入当前实例的 userId、userProfile、workId 等
    • touchSession:刷新会话心跳,过期则清理
  • UserAuthService(会话与凭证)
    • 前台登录成功:生成 shell、写入 user_id/shell/ontime/field,并生成静态 CSRF 令牌
    • remember token:签发并设置 DOU_TOKEN Cookie,用于后续自动恢复

架构总览

下图展示一次受保护的前台请求在中间件链中的完整流转:

sequenceDiagram
participant Client as "浏览器"
participant MW as "UserAuthMiddleware"
participant Base as "AbstractUserAuthMiddleware"
participant Policy as "UserAuthPolicy"
participant Guard as "Auth(front)"
participant Service as "UserAuthService"
participant Ctrl as "控制器"
Client->>MW : 发起受保护请求
MW->>Base : handle(next)
Base->>Policy : resolve(module, action, sub, parent, auth_modes, work_required)
Policy-->>Base : {mode, workRequired}
alt mode == public
Base-->>Ctrl : 放行
else mode != public
Base->>MW : resolveContext()
MW->>Guard : resolveUserContext()
Guard->>Service : 读取/恢复 session 或 remember token
Service-->>Guard : 用户上下文
Guard-->>MW : {ok, userId, userProfile, workId...}
alt ok == false 且 mode == required
MW->>MW : rejectUnauthenticated()
MW-->>Client : 重定向登录页 / JSON 401
else ok == true
MW->>Guard : hydrate(context)
alt workRequired && !hasWorkIdentity
MW->>MW : rejectForbidden()
MW-->>Client : 重定向用户中心
else 通过
Base-->>Ctrl : 放行
end
end
end

详细组件分析

组件A:前台认证中间件(UserAuthMiddleware)

职责边界

  • 指定前台配置文件路径
  • 通过 auth('front') 解析上下文并注入身份缓存
  • 未登录拒绝:XHR from=js 返回 JSON 401 并附带 jump_url;普通页面重定向到登录页并附加 redirect
  • 无工作端权限:重定向到用户中心

关键流程

  • resolveContext:调用 auth('front')->resolveUserContext() 获取用户上下文
  • inject:调用 auth('front')->hydrate(context) 注入身份缓存
  • hasWorkIdentity:判断当前用户是否具备有效工作端身份
  • rejectUnauthenticated:区分 XHR 与普通请求,分别返回 JSON 或重定向
  • rejectForbidden:无工作端身份时重定向到用户中心
flowchart TD
Start(["进入 rejectUnauthenticated"]) --> CheckXHR{"from === 'js' ?"}
CheckXHR --> |是| ThrowJson["抛出 ApiResponse 401<br/>包含 jump_url"]
CheckXHR --> |否| BuildRedirect["构造登录页URL<br/>拼接 redirect=当前URL"]
BuildRedirect --> Redirect["抛出 HttpResponseException(redirect)"]
ThrowJson --> End(["结束"])
Redirect --> End

组件B:通用鉴权骨架(AbstractUserAuthMiddleware)

职责边界

  • 加载前台鉴权配置(auth_modes、work_required)
  • 基于路由段与策略表决策 mode 与 workRequired
  • 三态分支:public 直达;optional 失败放行;required 失败拒绝
  • 注入身份后按 work_required 决定是否要求工作端身份

关键流程

  • __construct:加载配置文件
  • handle:解析路由段、调用策略、执行三态分支、注入身份、检查工作端、放行
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
}

组件C:鉴权策略(UserAuthPolicy)

职责边界

  • 根据 module/action/sub/parent 构建候选键列表
  • 从配置表匹配最细粒度的模式(public/optional/required)
  • 判定是否命中 work_required

关键流程

  • buildCandidates:精确 > 父段 > 模块根
  • resolve:遍历候选键,取首个合法模式;同时判定 workRequired
flowchart TD
S(["开始"]) --> C1["构建候选键列表"]
C1 --> M{"匹配到模式?"}
M --> |是| SetMode["设置 mode = 匹配值"]
M --> |否| DefaultMode["mode = optional"]
SetMode --> W{"命中 work_required?"}
DefaultMode --> W
W --> |是| WorkTrue["workRequired = true"]
W --> |否| WorkFalse["workRequired = false"]
WorkTrue --> E(["结束"])
WorkFalse --> E

组件D:前台 Guard(Auth)与会话管理

职责边界

  • resolveUserContext:优先从 Session 恢复,否则尝试 remember token
  • verifySession/matchSession:校验 session shell 与数据库一致
  • hydrate:将上下文写入当前实例的 userId、userProfile、workId 等
  • touchSession:刷新会话心跳,过期则清理

关键流程

  • 登录成功:UserAuthService 写入 session(user_id/shell/ontime/field)并生成静态 CSRF 令牌
  • 自动恢复:若 Session 缺失,尝试从 DOU_TOKEN Cookie 恢复
  • 心跳刷新:每次访问更新 ontime,超过阈值则清理会话
sequenceDiagram
participant Guard as "Auth(front)"
participant Service as "UserAuthService"
participant DB as "数据库"
participant Session as "Session"
Note over Guard : 解析用户上下文
Guard->>Session : 读取 user_id/shell/ontime/field
alt 会话有效
Guard->>DB : matchSession(userId, shell)
DB-->>Guard : 用户行
Guard->>Session : touchSession()
else 会话无效
Guard->>Service : restoreFromToken()
Service-->>Guard : 是否恢复成功
alt 恢复成功
Guard->>Session : 重新写入会话
else 恢复失败
Guard-->>Guard : 返回空上下文
end
end

组件E:前台白名单与重定向规则

  • 白名单配置位置:front/init/middleware.php
  • 模式说明:
    • public:匿名可访问,且不尝试 Session 登录态
    • optional:尝试登录态,失败不拦截(默认兜底)
    • required:必须登录
  • 典型公开页面:
    • 登录/注册/找回密码/验证码相关:user/login、user/register、user/password_reset、user/verification
    • 预约公开页面:book/default、book/index、book/list、book/class、book/show、book/schedule、book/date、book/time
    • 插件回调:plugin/notify、plugin/finish、plugin/status
    • 公共浏览模块:article、brand、cases、product、service 等
  • 重定向规则:
    • 未登录且 required:普通页面重定向到登录页,并附加 redirect=当前URL;XHR from=js 返回 JSON 401 并附带 jump_url
    • 无工作端权限:重定向到用户中心

依赖关系分析

  • UserAuthMiddleware 依赖:
    • AbstractUserAuthMiddleware:继承复用通用骨架
    • UserAuthPolicy:策略决策
    • Auth(front):会话解析与身份注入
    • UserAuthService:会话写入与 remember token
  • 配置依赖:
    • front/init/middleware.php:auth_modes 与 work_required 定义
graph LR
UAM["UserAuthMiddleware"] --> AUAM["AbstractUserAuthMiddleware"]
UAM --> AP["UserAuthPolicy"]
UAM --> AG["Auth(front)"]
AG --> UA["UserAuthService"]
AUAM --> CFG["front/init/middleware.php"]

性能考虑

  • 策略匹配开销低:仅字符串比较与数组查找,时间复杂度近似 O(1)
  • 会话校验仅在 optional/required 分支执行,public 分支零开销
  • remember token 恢复仅在 Session 缺失时触发,避免多余数据库查询
  • 心跳刷新按需进行,避免频繁写 Session
  • 建议:
    • 合理划分 public/optional/required,减少不必要的登录态解析
    • 对高频只读页面尽量设为 optional,降低强制登录带来的额外开销
    • 注意会话超时阈值,避免频繁清理导致用户体验下降

故障排查指南

常见问题与定位要点

  • 未登录仍被重定向:
    • 检查路由是否在 auth_modes 中声明为 required
    • 确认 XHR 是否携带 from=js 以期望 JSON 401
  • 登录后仍显示未登录:
    • 检查 Session 是否写入 user_id/shell/ontime/field
    • 核对 shell 计算是否与 UserAuthService 一致
    • 检查 remember token 是否过期或失效
  • 工作端权限不足:
    • 检查 work_required 配置是否命中该路由
    • 确认 hasWorkIdentity 判断条件(workId > 0)
  • 会话过期:
    • 检查 touchSession 是否按时刷新 ontime
    • 确认超时阈值与业务预期一致

结论

前台认证中间件通过策略化配置与模板方法实现了高内聚、低耦合的鉴权体系:

  • 策略层清晰界定 public/optional/required,便于安全治理与扩展
  • 基类统一处理三态分支与工作端校验,子类仅需关注前台特有行为
  • Guard 与会话服务解耦,支持 remember token 与 Session 双通道恢复
  • 拒绝响应区分 Web 与 XHR,提升前后端交互体验
  • 通过白名单与重定向规则,既保障安全又兼顾可用性

附录

配置选项与使用示例

  • 配置文件:front/init/middleware.php
  • 主要选项:
    • auth_modes:按 module/sub/action 维度声明 public/optional/required
    • work_required:命中后需具备工作端身份
  • 使用示例:
    • 新增需登录模块:在 auth_modes 中登记为 required
    • 新增公开页面:在 auth_modes 中登记为 public
    • 需要工作端权限:在 work_required 中添加对应路由键

前台与 API 端认证差异

  • 前台:基于 Session + remember token,拒绝时重定向或返回 JSON 401(XHR)
  • API:无状态 token 鉴权,拒绝时返回 JSON 错误码
  • 原因:
    • 前台为浏览器环境,适合重定向与页面级跳转
    • API 为客户端/小程序调用,需结构化 JSON 响应以便前端处理

多端会话同步最佳实践

  • 统一凭据来源:前台与小程序均通过同一认证入口(如手机号+验证码)完成登录
  • 会话存储:
    • 前台:Session + remember token(DOU_TOKEN)
    • 小程序:使用 API token(由 UserAuthService 签发)
  • 同步策略:
    • 登录成功后,前台写入 Session,小程序侧保存 API token
    • 跨端操作通过 API 调用,保证状态一致性
  • 注意事项:
    • 避免在同一会话中混用不同凭据类型
    • 登出时清除所有端凭据(Session、Cookie、API token)
添加日期:2026-10-05