csrf token 挡不住重复提交,因其会话级、可重用;防重放须用一次性 nonce,需密码学随机生成、redis/db 存储带过期、签名传输、提交后立即删除,并在服务端严格闭环校验。

为什么 csrf_token 挡不住重复提交?
因为 csrf_token 是会话级的,只要用户没退出、cookie 没过期,同一个 token 就能反复用。表单提交成功后页面没刷新,用户狂点“提交”按钮,后端每次收到的都是合法 CSRF token——它只防跨站,不防重放。
真正要拦住重复提交,得靠一次性凭证:nonce。它必须服务端生成、一次有效、带时效、绑定用户动作(比如创建订单),且不能和 CSRF token 混用。
generate_nonce() 怎么写才安全?
别用时间戳或自增 ID 做 nonce,容易被预测或重放。必须用密码学安全随机数 + 用户上下文签名。
- 用
secrets.token_urlsafe(32)生成随机字符串,不是random模块 - 把 nonce 存进 Redis,设置 5 分钟过期,key 建议拼上用户 ID 和操作类型:
f"nonce:submit_order:{user_id}" - 如果要用数据库存,务必加唯一索引 +
created_at字段,避免脏数据堆积 - 不要把 nonce 直接塞进隐藏字段明传,至少用
itsdangerous签名:用URLSafeTimedSerializer包一层,防止篡改
Flask 表单里怎么塞和验 nonce?
关键在「生成 → 渲染 → 提交 → 校验 → 消费」闭环不能断。漏掉任意一环,就等于没做。
caozha-order是一个通用的竞价页订单管理系统,基于开源的caozha-admin开发,支持订单管理、订单回收站、产品管理、批量上传订单、批量导出订单(支持导出格式:.xls,.xlsx,.csv)、检测订单重复、竞价页的下单表单调用等功能,内置灵活的查看订单权限设置机制。系统特点:易上手,零门槛,界面清爽极简,极便于二次开发。
立即学习“Python免费学习笔记(深入)”;
- 渲染表单前,在视图里调
generate_nonce(),把结果存在 session 或模板变量里,再传给前端:<input type="hidden" name="nonce" value="{{ nonce }}"> - 接收 POST 时,先取
request.form.get("nonce"),再用同一套逻辑反解或查 Redis;验证失败直接return abort(400),别进业务逻辑 - 校验通过后,立刻从 Redis 删除该 nonce——这是最常漏的一步,不删就形同虚设
- 如果用了
URLSafeTimedSerializer,注意它的max_age要和 Redis 过期时间对齐,否则可能 token 没过期但签名已失效
为什么前端防抖不能替代服务端 nonce?
JS 防抖只是让按钮点一次变灰,但它挡不住 Postman 重放、curl 提交、或者用户禁用 JS 后手动发请求。所有客户端限制都可绕过。
服务端 nonce 是最后一道防线,而且它还能帮你发现异常行为:比如同一用户 1 秒内提交 5 个不同 nonce,大概率是脚本在扫。
真正复杂的点在于状态同步——如果部署多台 Web 实例,Redis 必须是共享的;如果用了异步任务处理表单(比如 Celery),nonce 校验必须在任务入队前完成,不能丢到队列里再验。









