简介
本文件面向 DouPHP 的会话管理机制,覆盖以下主题:
- 会话存储策略:默认基于 PHP 原生 $_SESSION(文件/Redis/数据库由运行环境配置决定),并说明插件中提供的 SQLite 自定义存储实现。
- 会话生命周期:创建、更新、销毁与垃圾回收。
- 安全加固:会话固定攻击防护、会话劫持检测思路、跨站请求伪造(CSRF)防护。
- 分布式会话同步:多服务器共享会话的方案建议。
- 性能优化与内存管理:会话数据大小控制、读写频率优化、GC 策略等。
项目结构
DouPHP 的会话能力由“启动期硬化 + 门面封装 + 中间件校验”三部分构成:
- 启动期:在应用初始化阶段对会话 Cookie 进行安全硬化,然后启动会话。
- 门面层:提供统一的 Session 门面,业务侧通过门面读写命名空间化的会话数据。
- 中间件层:前后端分别提供 CSRF 中间件,结合前端脚本自动注入令牌,保障表单/AJAX 安全。
graph TB
A["应用入口<br/>InitTrait::startSession()"] --> B["安全配置加载<br/>config/security.php"]
B --> C["会话 Cookie 硬化<br/>httponly/samesite/secure/strict_mode"]
C --> D["session_start()<br/>启用 PHP 会话"]
D --> E["Session 门面<br/>core/facade/Session.php"]
E --> F["Session 服务<br/>core/infra/session/Session.php"]
F --> G["$_SESSION[DOU_ID] 命名空间"]
H["前台 CSRF 中间件<br/>front/middleware/CsrfMiddleware.php"] --> I["AJAX/表单令牌校验"]
J["后台 CSRF 中间件<br/>admin/middleware/CsrfMiddleware.php"] --> I
K["前端 CSRF 脚本<br/>admin/view/js/dou.csrf.js"] --> I
图示来源
- core/init/InitTrait.php:214-258
- config/security.php:79-85
- core/facade/Session.php:23-59
- core/infra/session/Session.php:21-320
- front/middleware/CsrfMiddleware.php:24-96
- admin/middleware/CsrfMiddleware.php:24-54
- admin/view/js/dou.csrf.js:13-60
核心组件
- 会话服务类:提供 get/set/has/del/clear/push/pull/increment/decrement/forget 等方法,统一以 DOU_ID 为命名空间键前缀,避免不同模块间冲突。
- 会话门面:静态门面将底层 Session 服务注册到容器,业务侧通过门面调用,便于测试替换。
- 会话启动与安全硬化:在应用初始化早期读取安全配置,设置 strict_mode、only_cookies、httponly、samesite、secure,再启动会话。
- CSRF 中间件:前后端分别实现一次性令牌或静态令牌模型,配合前端脚本自动注入 X-CSRF-Token 头,拦截非法请求。
架构总览
下图展示从请求进入、会话启动、会话读写到 CSRF 校验的整体流程。
sequenceDiagram
participant Client as "客户端"
participant Init as "InitTrait : : startSession()"
participant SessFacade as "Session 门面"
participant SessSvc as "Session 服务"
participant CsrfMW as "CSRF 中间件"
participant Store as "会话存储(文件/Redis/DB)"
Client->>Init : 发起 HTTP 请求
Init->>Init : 读取 config/security.php 的 session 配置
Init->>Init : 设置 strict_mode / only_cookies / httponly / samesite / secure
Init->>Store : session_start()
Client->>SessFacade : 读取/写入会话数据
SessFacade->>SessSvc : get()/set()/...
SessSvc->>Store : 持久化/读取 $_SESSION[DOU_ID]
Client->>CsrfMW : 提交表单或 AJAX
CsrfMW->>CsrfMW : 校验一次性/静态令牌
CsrfMW-->>Client : 允许或拒绝
图示来源
- core/init/InitTrait.php:214-258
- config/security.php:79-85
- core/facade/Session.php:23-59
- core/infra/session/Session.php:21-320
- front/middleware/CsrfMiddleware.php:24-96
- admin/middleware/CsrfMiddleware.php:24-54
详细组件分析
会话服务与门面
- 会话服务负责在 DOU_ID 命名空间下安全地读写数组字段,支持二级键、一次性 flash 消息、计数器自增/自减等常用操作。
- 门面提供静态方法访问,内部委托给容器中的 Session 服务实例,便于测试时替换。
classDiagram
class SessionFacade {
+get(key, default, subKey)
+arr(key)
+set(key, value, subKey)
+has(key, subKey)
+del(key, subKey)
+clear()
+push(key, value)
+pull(key, default)
+increment(key, by)
+decrement(key, by)
+forget(key, value)
+setFlash(key, value)
+getFlash(key, default)
+pullAllFlashes()
}
class SessionService {
-string DOU_ID
+get(key, default, subKey) mixed
+arr(key) array
+set(key, value, subKey) void
+has(key, subKey) bool
+del(key, subKey) void
+clear() void
+push(key, value) void
+pull(key, default) mixed
+increment(key, by) int
+decrement(key, by) int
+forget(key, value) void
+setFlash(key, value) void
+getFlash(key, default) mixed
+pullAllFlashes() array
}
SessionFacade --> SessionService : "委托调用"
图示来源
- core/facade/Session.php:23-59
- core/infra/session/Session.php:21-320
会话启动与安全硬化
- 在应用初始化阶段,先读取安全配置中的 session 块,设置严格模式、仅 Cookie、HttpOnly、SameSite、Secure,再调用 session_start()。
- 该流程确保会话 ID 不被外部传入,Cookie 具备基本防窃取与跨站限制。
flowchart TD
Start(["开始"]) --> LoadCfg["读取 config/security.php 的 session 配置"]
LoadCfg --> SetStrict{"use_strict_mode ?"}
SetStrict --> |是| EnableStrict["启用 session.use_strict_mode"]
SetStrict --> |否| SkipStrict["跳过严格模式"]
EnableStrict --> SetCookies["设置 Cookie: httponly/samesite/secure"]
SkipStrict --> SetCookies
SetCookies --> StartSession["调用 session_start()"]
StartSession --> End(["结束"])
图示来源
- core/init/InitTrait.php:214-258
- config/security.php:79-85
CSRF 防护机制
- 前台与后台分别实现 CSRF 中间件,采用一次性令牌或静态令牌模型;前端脚本自动为非幂等请求注入 X-CSRF-Token 头。
- 路由级可声明式豁免特定接口(如支付回调、安装步骤等)。
sequenceDiagram
participant Browser as "浏览器"
participant FrontMW as "前台 CSRF 中间件"
participant AdminMW as "后台 CSRF 中间件"
participant JS as "dou.csrf.js"
Browser->>JS : 页面加载获取 meta csrf-token
JS->>Browser : 非幂等请求自动注入 X-CSRF-Token
Browser->>FrontMW : POST/PUT/PATCH/DELETE 请求
FrontMW->>FrontMW : 校验一次性/静态令牌
FrontMW-->>Browser : 通过或拒绝
Browser->>AdminMW : 后台敏感操作
AdminMW->>AdminMW : 校验静态/一次性令牌
AdminMW-->>Browser : 通过或拒绝
图示来源
- front/middleware/CsrfMiddleware.php:24-96
- admin/middleware/CsrfMiddleware.php:24-54
- admin/view/js/dou.csrf.js:13-60
会话存储策略
- 默认存储:使用 PHP 原生 $_SESSION,具体后端由运行环境决定(文件、Redis、数据库均可通过 PHP 配置切换)。
- 插件示例:插件目录包含基于 SQLite 的自定义会话存储实现,演示了如何通过 session_set_save_handler 接管 read/write/destroy/gc。
- 注意:插件中的 LtSession/LtSessionSqlite 属于第三方 SDK 示例,并非 DouPHP 主框架默认会话后端;如需在生产中使用,应将其集成到应用启动流程并正确配置。
flowchart TD
A["选择会话后端"] --> B{"使用 PHP 默认后端?"}
B --> |是| C["文件/Redis/数据库<br/>由 php.ini 或扩展配置"]
B --> |否| D["自定义存储实现<br/>例如 SQLite"]
D --> E["实现 open/read/write/destroy/gc"]
E --> F["注册 session_set_save_handler()"]
C --> G["session_start() 后直接读写 $_SESSION"]
F --> G
图示来源
- plugin/alipay/sdk/lotusphp_runtime/Session/Session.php:19-43
- plugin/alipay/sdk/lotusphp_runtime/Session/Store/SessionStoreSqlite.php:16-105
会话生命周期管理
- 创建:应用初始化阶段完成 Cookie 硬化后调用 session_start() 创建会话。
- 更新:业务侧通过 Session 门面 set/push/increment 等方法修改 $_SESSION[DOU_ID],PHP 会在响应结束时自动持久化。
- 销毁:登出或安全事件触发时,清空命名空间或使用 destroy 逻辑删除会话记录(自定义存储需实现 destroy)。
- 垃圾回收:默认由 PHP GC 根据 gc_maxlifetime 清理过期会话;SQLite 示例在 close 时执行 gc 删除过期记录。
flowchart TD
S(["会话创建"]) --> U["会话更新<br/>set/push/increment"]
U --> P{"是否登出/失效?"}
P --> |是| D["销毁会话<br/>clear/destroy"]
P --> |否| T["等待 GC<br/>按 gc_maxlifetime 清理"]
D --> E(["结束"])
T --> E
图示来源
- core/init/InitTrait.php:214-258
- core/infra/session/Session.php:157-169
- plugin/alipay/sdk/lotusphp_runtime/Session/Store/SessionStoreSqlite.php:46-105
分布式会话同步方案
- 目标:在多服务器环境下共享同一会话状态。
- 推荐做法:
- 将 PHP 会话后端切换为 Redis,集中存储所有服务器的会话数据。
- 配置相同的会话 Cookie 域名与路径,确保各节点能识别同一会话 ID。
- 若使用自定义存储(如 SQLite/数据库),需保证多进程并发写锁与高可用。
- 注意事项:
- 保持 SameSite 与 Secure 策略一致,避免跨域或混合内容问题。
- 合理设置 gc_maxlifetime,避免频繁 GC 造成性能抖动。
(本节为通用实践建议,不直接引用具体代码文件)
依赖关系分析
- 启动期依赖安全配置,确保会话 Cookie 安全参数生效。
- 会话门面依赖容器中的 Session 服务实例。
- CSRF 中间件依赖前端脚本注入令牌,并在服务端校验。
- 插件会话存储依赖 PHP 扩展与配置文件。
graph LR
SecCfg["config/security.php"] --> Init["InitTrait::startSession()"]
Init --> Facade["Session 门面"]
Facade --> Service["Session 服务"]
Service --> Store["会话存储(文件/Redis/DB)"]
FrontMW["前台 CSRF 中间件"] --> Req["请求处理"]
AdminMW["后台 CSRF 中间件"] --> Req
Js["dou.csrf.js"] --> FrontMW
Js --> AdminMW
图示来源
- config/security.php:79-85
- core/init/InitTrait.php:214-258
- core/facade/Session.php:23-59
- core/infra/session/Session.php:21-320
- front/middleware/CsrfMiddleware.php:24-96
- admin/middleware/CsrfMiddleware.php:24-54
- admin/view/js/dou.csrf.js:13-60
性能与内存管理
- 会话数据体积控制:避免在 $_SESSION[DOU_ID] 中存放大对象或冗余信息,必要时拆分到缓存或数据库,仅保留关键标识。
- 读写频率优化:合并多次 set 操作,减少序列化开销;合理使用 increment/decrement 做轻量计数。
- GC 策略:调整 gc_maxlifetime 与 gc_probability/gc_divisor,平衡过期清理频率与系统负载。
- 后端选择:高并发场景优先使用 Redis 作为会话后端,降低磁盘 IO 与锁竞争。
- 自定义存储:SQLite 示例在 close 时执行 gc,生产环境需评估并发写与锁粒度。
(本节为通用实践建议,不直接引用具体代码文件)
故障排查指南
- 会话无法创建:检查是否已调用 startSession 且未重复启动;确认 strict_mode 与 only_cookies 设置是否正确。
- Cookie 未下发:核对 httponly、secure、samesite 配置是否与部署环境匹配(HTTPS/跨域)。
- CSRF 校验失败:确认前端脚本是否注入 X-CSRF-Token;检查路由是否被豁免;验证一次性/静态令牌生成与校验逻辑。
- 会话数据丢失:检查会话后端是否可用(文件权限/Redis 连接/数据库表结构);确认 gc_maxlifetime 是否过短。
- 登录态异常:检查登出流程是否清除了命名空间;确认会话 ID 是否在敏感操作后重新生成(防止会话固定)。
结论
DouPHP 的会话管理以“安全硬化 + 门面封装 + CSRF 中间件”为核心,默认使用 PHP 原生会话后端,并通过插件示例展示了自定义存储的实现方式。生产环境中建议:
- 使用 Redis 作为会话后端以提升性能与可扩展性。
- 保持 Cookie 安全策略与部署环境一致。
- 严格控制会话数据大小与读写频率。
- 完善 CSRF 防护与登出流程,防范会话固定与劫持风险。
附录
- 相关配置项参考:
- 会话 Cookie 硬化:httponly、secure、samesite、use_strict_mode。
- CSRF 令牌模型:前台一次性令牌与静态令牌并存;后台统一静态令牌,部分匿名流程使用一次性令牌。
- 插件会话存储:SQLite 示例提供了 read/write/destroy/gc 的完整实现,可作为自定义后端参考。