防重复提交
提交按钮被双击、网络超时后自动重试、浏览器回退再次提交,都可能让同一个表单被服务器处理两次。使用 Token 令牌机制可以有效地防止 CSRF 攻击和重复提交:在提交表单时,服务器会生成一个 Token 令牌并将其存储在 Redis 中,然后作为表单的隐藏字段或 URL 参数传递给客户端,提交时校验、用完即删。
为什么这个问题值得处理
重复提交的业务后果往往比表面看起来严重:下单接口被处理两次就是两笔订单,扣款请求被重试两次就是重复扣款。前端把提交按钮置灰只能挡住手滑,挡不住网络层的自动重试,更挡不住恶意构造的请求,所以这层防护最终要落在服务端。
CSRF 攻击的场景则是攻击者诱导用户的浏览器向目标站点发起请求,它能得逞的原因是浏览器会自动携带 Cookie。而 Token 不放在 Cookie 里,第三方站点拿不到这个值,因此一套 Token 机制可以同时防住这两类问题。
基本原理
Token 令牌机制是一种常用的 Web 应用程序防止重复提交和 CSRF 攻击的方法。它的基本思想是在每次提交表单时,服务器会生成一个 Token 令牌,并将其存储在 Redis 中。然后,将这个 Token 令牌作为表单的一个隐藏字段或 URL 参数传递给客户端。客户端提交表单时,将这个 Token 令牌一并提交给服务器。服务器在处理表单时,会检查这个 Token 令牌是否正确,并在处理完表单后删除这个 Token 令牌。
两个防护目标在机制上各有侧重:防重复提交靠的是"校验通过后立即删除",同一个 Token 第二次到达时已经不存在,请求会被拒绝;防 CSRF 靠的是 Token 只下发给合法页面,攻击者构造的请求里没有它。Redis 在这里承担两件事:集中存储,让多实例部署时任何一台机器都能完成校验;过期控制,利用 TTL 自动清理没有被消费的 Token。
实现步骤
1)在服务器端生成一个 Token 令牌,并将其存储在 Redis 中。Token 令牌可以使用随机数、UUID 或其他方法生成,需要保证其足够随机和唯一。写入时顺便设置过期时间:
# NX 保证不覆盖已有 Token,EX 设置过期时间(秒),避免无效 Token 长期堆积
SET submit:token:<tokenValue> 1 EX 600 NX
2)将这个 Token 令牌传递给客户端。如果是表单提交,可以将 Token 令牌作为一个隐藏字段传递;如果是 AJAX 请求,可以将 Token 令牌作为一个 HTTP 头传递。
3)客户端提交表单时,将这个 Token 令牌一并提交给服务器。如果是表单提交,可以在表单的提交事件中添加一段 JavaScript 代码,将 Token 令牌添加为一个隐藏字段;如果是 AJAX 请求,可以在发送请求前将 Token 令牌添加为一个 HTTP 头。
4)服务器在处理表单时,会检查这个 Token 令牌是否正确。如果 Token 令牌正确,则说明这是一个有效的请求,可以继续处理;否则,说明这是一个重复提交或 CSRF 攻击,应该拒绝处理。
5)在处理完表单后,服务器需要从 Redis 中删除这个 Token 令牌,以防止重复使用。
第 4、5 步的"校验"和"删除"必须作为一个原子操作完成。如果先 GET 再 DEL 分两步执行,两个并发请求可能同时通过校验,防重复提交就失效了。可以用一段 Lua 脚本让 Redis 原子地完成校验加删除:
-- 校验并删除:Token 存在则删除并返回 1,否则返回 0
-- 整段脚本在 Redis 中原子执行,并发请求只有一个能成功
if redis.call('GET', KEYS[1]) then
return redis.call('DEL', KEYS[1])
else
return 0
end
安全注意事项
Token 令牌机制可以有效地防止重复提交和 CSRF 攻击,但是需要注意保护 Token 令牌的安全性,否则可能会被攻击者利用。具体来说,需要注意以下几点:
Token 令牌需要足够随机和唯一,否则可能会被攻击者猜测或重复使用。
Token 令牌需要保护其传输的安全性,否则可能会被攻击者截获或篡改。传输一律走 HTTPS,不要把 Token 拼在会被记录到日志的 URL 里。
Token 令牌需要设置有效期限,否则可能会被攻击者重复使用。
Token 令牌需要保护其存储在 Redis 中的安全性,否则可能会被攻击者盗取或篡改。Redis 实例不应暴露在公网,并开启访问认证。
踩坑与注意
校验和删除分两步写是最常见的错误实现。压测或用户快速双击时,两个请求都能通过 GET 校验,各自处理一遍业务。务必用 Lua 脚本或等价手段保证原子性。
过期时间要结合用户实际填表时长来定。设得太短,用户填一个长表单的工夫 Token 已经过期,提交必然失败;设得太长,无效 Token 又会在 Redis 里堆积。
校验失败时前端要给出明确反馈,引导用户刷新页面重新获取 Token 后再提交,而不是静默丢弃请求,否则用户只会觉得"点了没反应"然后继续狂点。
另外要想清楚删除的时机:业务处理失败时是否归还 Token、允许用户直接重试,取决于具体业务对幂等的要求,没有统一答案。
小结
Token 令牌机制的核心是"一次性凭证":服务器发放、Redis 存储、提交校验、用完即删,一套流程同时解决了重复提交和 CSRF 两个问题。落地时重点盯住四件事——Token 的随机性、传输与存储的安全、合理的有效期,以及校验删除的原子性。这些做到了,它就是一个成本很低、收益明确的通用防护手段。
评论 / COMMENTS