文档目录
请求签名验证

简介

本文件面向 DouPHP 的 API 端,提供“请求签名验证”的完整说明与落地方案。内容覆盖:

  • HMAC-SHA256 签名生成与校验流程
  • 时间戳与防重放的时间窗口控制
  • 参数排序、空值处理、特殊字符转义规则
  • 中间件集成方式与配置方法
  • 客户端签名示例(步骤化)与常见错误调试方法

注意:当前仓库未内置统一的通用签名中间件;本文基于现有鉴权中间件与第三方插件中的 HMAC 实现,给出可落地的扩展方案。

项目结构

DouPHP 的 API 入口位于 api 目录,鉴权通过中间件管道执行。API 鉴权模式由配置文件集中声明,具体解析与拒绝策略由基类与子类协作完成。第三方插件中提供了成熟的 HMAC-SHA256 验签实现,可作为参考与复用。

graph TB
Client["客户端"] --> MW["API 中间件管道"]
MW --> AuthMW["用户认证中间件<br/>api/middleware/UserAuthMiddleware.php"]
AuthMW --> BaseMW["抽象鉴权基类<br/>core/foundation/middleware/AbstractUserAuthMiddleware.php"]
BaseMW --> Config["鉴权模式配置<br/>api/init/middleware.php"]
AuthMW --> Controller["业务控制器"]
Controller --> PluginHMAC["HMAC 验签参考实现<br/>plugin/square/SquareService.php"]

核心组件

  • 鉴权模式配置:在 api/init/middleware.php 中以键值对形式声明各模块/动作的鉴权级别(public/optional/required),并支持 work_required 子策略。
  • 用户认证中间件:api/middleware/UserAuthMiddleware.php 负责从请求头提取 Bearer Token,调用 auth('api') 解析上下文,并在失败时返回 JSON 错误。
  • 抽象鉴权基类:core/foundation/middleware/AbstractUserAuthMiddleware.php 提供模板方法,统一路由段解析、模式决策、身份注入与拒绝响应。
  • HMAC 验签参考:plugin/square/SquareService.php 实现了 HMAC-SHA256 + Base64 的 Webhook 签名校验,使用 hash_equals 进行时序安全比较。

架构总览

下图展示“请求签名验证”在 API 请求链路中的位置:客户端携带签名与时间戳发起请求,进入中间件管道后先做鉴权模式判定,再执行签名校验(建议新增签名中间件),最后进入控制器。

sequenceDiagram
participant C as "客户端"
participant P as "中间件管道"
participant A as "用户认证中间件"
participant S as "签名校验(建议新增)"
participant Ctrl as "业务控制器"
C->>P : HTTP 请求(含 timestamp, sign, params...)
P->>A : 鉴权模式判定(public/optional/required)
A-->>P : 通过或拒绝(JSON 401/403)
P->>S : 执行签名校验(HMAC-SHA256 + 时间窗口)
S-->>P : 通过或拒绝(400/403)
P->>Ctrl : 进入控制器处理
Ctrl-->>C : 业务响应

详细组件分析

签名算法与参数规范

  • 算法:HMAC-SHA256,输出为十六进制字符串(也可选择 Base64,需前后端一致)。
  • 参与签名的参数集合:除 sign 本身外的所有业务参数,以及可选的 timestamp、nonce 等。
  • 参数排序:按参数名 ASCII 升序排列(ksort),确保拼接串稳定。
  • 空值处理:值为空字符串或 null 的参数不参与签名(过滤空值)。
  • 特殊字符:保持原始字节序列参与计算,不进行额外编码;如需 URL 传输,请在网络层做标准编码,但签名前必须还原为原始值。
  • 拼接串:将排序后的参数以 key=value 形式用 & 连接成字符串。
  • 密钥:服务端与客户端共享的 secret,严禁硬编码,建议使用环境变量或配置中心管理。

时间戳与防重放

  • 时间戳:请求携带 timestamp(秒级 Unix 时间),服务端校验其是否在允许的时间窗口内(例如 ±5 分钟)。
  • 时间窗口:超过窗口的请求直接拒绝,防止重放攻击。
  • 时钟同步:建议 NTP 同步服务器时间;若跨机房/多实例,需保证时间一致性。
  • 可选增强:结合 nonce(一次性随机数)+ 缓存去重,进一步抵御重放。

中间件集成与配置

  • 鉴权模式:在 api/init/middleware.php 中为需要签名的模块/动作设置 required,确保进入鉴权流程。
  • 用户认证中间件:api/middleware/UserAuthMiddleware.php 已具备 JSON 拒绝响应能力,便于扩展签名校验逻辑。
  • 抽象基类:core/foundation/middleware/AbstractUserAuthMiddleware.php 提供 handle 模板方法,可在其中插入签名校验步骤。
  • 推荐做法:新建一个 SignatureMiddleware,继承或参照 UserAuthMiddleware 的模式,在 handle 中优先执行签名校验,再通过鉴权流程。

后端校验流程(建议新增签名中间件)

flowchart TD
Start(["接收请求"]) --> Extract["提取参数<br/>timestamp, sign, params..."]
Extract --> Filter["过滤空值参数"]
Filter --> Sort["按 key 升序排序"]
Sort --> Build["构建待签名字符串"]
Build --> TS{"时间戳有效?"}
TS -- 否 --> RejectTS["拒绝: 时间戳无效"]
TS -- 是 --> Calc["计算期望签名<br/>HMAC-SHA256(secret, 待签名字符串)"]
Calc --> Compare["时序安全比较<br/>hash_equals(期望, 传入sign)"]
Compare -- 否 --> RejectSig["拒绝: 签名不匹配"]
Compare -- 是 --> Next["放行至后续中间件/控制器"]
RejectTS --> End(["结束"])
RejectSig --> End
Next --> End

第三方参考实现(Square Webhook 验签)

  • 该实现采用 HMAC-SHA256 + Base64,并使用 hash_equals 进行常量时间比较,避免侧信道泄露。
  • 适用于理解“如何正确构造消息体、如何比较签名”的最佳实践。

与现有安全机制的关系

  • CSRF:后台存在独立的 CSRF 中间件,用于表单提交防护,与 API 签名不同域,互不影响。
  • 验证码:核心验证码模块使用 HMAC 存储与一次性消费,体现系统对 HMAC 的安全使用习惯。

依赖关系分析

  • 中间件依赖:
    • api/middleware/UserAuthMiddleware.php 依赖 core/foundation/middleware/AbstractUserAuthMiddleware.php 提供的模板方法与配置加载。
    • 鉴权模式来源于 api/init/middleware.php。
  • 签名实现参考:
    • plugin/square/SquareService.php 展示了 HMAC-SHA256 的正确用法,可作为签名中间件的实现参考。
classDiagram
class AbstractUserAuthMiddleware {
+handle(next)
-loadConfig()
-resolveContext()
-inject(context)
-hasWorkIdentity()
-rejectUnauthenticated()
-rejectForbidden()
}
class UserAuthMiddleware {
+configFile()
+resolveContext()
+inject(context)
+hasWorkIdentity()
+rejectUnauthenticated()
+rejectForbidden()
}
class SquareService {
+verifyWebhookSignature(payload, signature, notificationUrl, signatureKey) bool
}
UserAuthMiddleware --|> AbstractUserAuthMiddleware : "继承"
UserAuthMiddleware ..> SquareService : "参考HMAC实现"

性能考虑

  • 签名计算复杂度:HMAC-SHA256 为 O(n),n 为待签名字符串长度;通常开销可忽略。
  • 时间窗口检查:O(1) 时间比较,极低成本。
  • 建议:
    • 将签名校验置于鉴权之前,尽早拒绝非法请求,减少后续资源消耗。
    • 对高频接口启用限流中间件,降低被暴力破解风险。
    • 避免在签名中包含大体积字段(如 base64 图片),必要时在服务端拆分或改用分片上传。

故障排查指南

  • 签名不匹配
    • 检查参数是否包含空值且未按规则过滤。
    • 确认参数排序是否为 ASCII 升序。
    • 确认密钥一致且未发生换行/空格差异。
    • 对比待签名字符串是否与客户端完全一致(包括大小写、顺序)。
  • 时间戳无效
    • 检查客户端与服务端时间是否同步。
    • 调整时间窗口范围,避免跨时区或网络延迟导致误判。
  • 请求被拒绝
    • 确认 api/init/middleware.php 中对应模块/动作的鉴权模式是否正确。
    • 检查中间件是否按顺序执行,签名校验应在鉴权之后或与之并列。
  • 日志定位
    • 在签名校验失败分支记录关键信息:timestamp、sign、params 摘要(脱敏)、期望签名摘要(脱敏)。
    • 结合限流与访问日志,快速定位异常来源。

结论

DouPHP 当前未内置通用 API 签名中间件,但已具备完善的鉴权中间件框架与 HMAC 安全实践参考。通过新增签名中间件,结合时间戳与可选 nonce,即可实现完整的请求签名验证与防重放保护。建议在鉴权模式配置中为敏感接口开启 required,并将签名校验纳入中间件管道,确保统一治理与安全基线。

附录

客户端签名步骤(示例)

  • 准备参数:收集所有业务参数,剔除空值;加入 timestamp、nonce(可选)。
  • 排序:按参数名 ASCII 升序排序。
  • 拼接:将 key=value 用 & 连接成待签名字符串。
  • 计算:使用 HMAC-SHA256(secret, 待签名字符串) 得到签名,转为十六进制或 Base64(与约定一致)。
  • 发送:将 sign、timestamp、nonce(如有)与业务参数一并发送至服务端。

服务端签名校验要点(建议新增中间件)

  • 读取请求参数:timestamp、sign、业务参数。
  • 过滤空值、排序、拼接。
  • 校验时间戳是否在窗口内。
  • 计算期望签名并与传入签名进行常量时间比较。
  • 通过则放行,否则返回 400/403。
添加日期:2026-10-05