防重放攻击需时间戳、nonce和签名三者协同:时间戳校验5分钟偏差,nonce用随机字符串+redis存储防重复,签名按参数排序拼接密钥sha256验签。

防重放攻击的关键是确保每个请求唯一且有时效性,核心靠时间戳、随机数(Nonce)和签名三者配合验证。
时间戳校验:限制请求有效期
客户端在请求中携带当前时间戳(如timestamp=1717023456),服务端接收后立即比对本地时间。若时间差超过预设阈值(如5分钟),直接拒绝请求。
- 时间戳必须是秒级整数,避免毫秒导致时区或精度误差
- 服务端不依赖客户端时钟,只以自身时间为基准计算偏差
- 建议统一用
time()生成,禁止使用date('U')等可能受时区影响的方式
Nonce随机数:防止同一请求重复提交
每次请求需附带唯一随机字符串(如nonce=abc123xyz789),服务端将其与时间戳组合后存入Redis(或数据库),设置与时间窗口一致的过期时间(如300秒)。收到新请求时先查是否存在该nonce,存在即为重放,直接拦截。
- Nonce推荐用
bin2hex(random_bytes(16))生成,保证高强度随机性 - 存储时建议拼接用户ID或AppID,避免跨账号冲突(如
nonce:{appid}:{nonce}) - 无需持久化长期保存,超时自动清理即可
签名验证:确保参数未被篡改
签名是对请求参数+密钥按约定规则生成的哈希值。典型流程:将所有非空参数按字段名升序排列,拼接成key1=value1&key2=value2格式,末尾追加secret_key,再用hash_hmac('sha256', $data, $secret)生成签名。服务端用同样逻辑重新计算并比对。
- 必须排除
sign、signature等签名字段本身参与计算 - 空值参数(
null、''、[])应过滤掉,避免签名不一致 - URL解码后再参与签名,防止编码差异导致验签失败
完整验证逻辑示例(PHP)
以下为精简可落地的服务端验证片段:
// 假设已从 $_GET 或 $_POST 获取参数数组 $params
$required = ['timestamp', 'nonce', 'sign'];
foreach ($required as $field) {
if (!isset($params[$field]) || !is_string($params[$field])) {
die('Missing required field: ' . $field);
}
}
<p>// 1. 时间戳检查
$ts = (int)$params['timestamp'];
$now = time();
if (abs($now - $ts) > 300) {
die('Timestamp expired');
}</p><p>// 2. Nonce 检查(Redis 示例)
$redisKey = 'nonce:' . $params['nonce'];
if ($redis->exists($redisKey)) {
die('Request replayed');
}
$redis->setex($redisKey, 300, 1); // 自动过期</p><p>// 3. 签名验证
unset($params['sign']);
ksort($params);
$data = http_build_query($params) . '&secret_key=' . $secret_key;
$expectedSign = hash_hmac('sha256', $data, $secret_key);</p><p>if (!hash_equals($expectedSign, $params['sign'])) {
die('Invalid signature');
}</p><p>// 验证通过,继续业务逻辑
</p>
这套机制不依赖HTTPS以外的传输层保护,但强烈建议始终启用HTTPS,否则时间戳、nonce、签名都可能被中间人截获并用于构造新请求。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











