签名超时本质是服务端与客户端时间窗口理解错位或系统时钟、解析逻辑隐性偏差,核心在timestamp校验环节;须用绝对差值校验、统一时区(推荐UTC)、读取原始输入并规范参数拼接。

签名超时不是“时间设短了”这么简单,而是服务端和客户端对时间窗口的理解错位,或系统时钟、解析逻辑存在隐性偏差。核心问题往往藏在 timestamp 校验环节,而不是 sign 字段本身。
时间戳校验必须用绝对差值,不能只比大小
很多开发者写成 $client_ts > time() - 300 && $client_ts ,这会漏掉客户端时间快于服务端的情况。攻击者或时区配置错误的设备,可能让 client_ts 比服务端大几分钟,直接被拒。
- 正确做法是计算绝对偏差:
abs($client_ts - time()) - 务必用
time(),不要用$_SERVER['REQUEST_TIME']——后者是 PHP 接收请求的起始秒数,不包含前置中间件耗时,精度低且不可控 - 如果服务部署在容器或云环境,需确认系统时钟是否启用 NTP 同步(如
chronyd或systemd-timesyncd),否则 drift 累积几小时后必然超时
客户端传的时间戳必须是整型秒级,且不带毫秒
JavaScript 的 Date.now() 返回毫秒时间戳,PHP 的 time() 是秒级。若前端没除以 1000,服务端拿到的是一个远大于当前时间的数字,一算就超 5 分钟。
- 前端应统一用
Math.floor(Date.now() / 1000)生成 timestamp - 服务端收到后,先强制转为整型:
(int)$params['timestamp'],避免字符串比较或浮点截断误差 - 调试时打印
var_dump($client_ts, time(), abs($client_ts - time())),一眼看出偏差方向和大小
中间件里别提前触发参数解析,否则 timestamp 可能被覆盖
ThinkPHP 的 $request->param() 会自动合并 GET 和 POST,并对值做 urldecode。如果 timestamp 是通过 query 传入(如 /api/user?timestamp=1725224257&sign=xxx),而你又在验签前调用了 $request->post() 或 $request->get(),框架可能已将原始字符串解析为整型,再拼签名原文时又转回字符串——看似一样,实则底层编码或空格处理有细微差异,导致哈希不一致,误判为“超时”。
立即学习“PHP免费学习笔记(深入)”;
- 验签中间件开头第一行就读原始输入:
$raw = file_get_contents('php://input'); - GET 参数单独用
$request->get()获取,POST/JSON 从$raw解析,再手动合并 - 所有参数值参与拼串前,统一
trim()+rawurlencode(),不依赖框架自动处理
跨时区或夏令时场景要显式约定时区
客户端若用本地时区生成 timestamp(比如 iOS Safari 默认行为),而服务端运行在 UTC+8,但未声明时区上下文,time() 返回的就是服务器本地时间,两者物理时刻相同但数值不同。
- 强制前后端都按 UTC 时间生成和校验 timestamp,服务端可加
date_default_timezone_set('UTC'); - 或在文档中明确要求客户端传 ISO 8601 格式时间字符串(如
2026-09-01T06:37:00Z),服务端用strtotime($ts . ' UTC')转换,更可靠 - 避免用
new DateTime()直接解析无时区标识的时间字符串,PHP 会按默认时区解释,易出错



















