在Web项目中,有一些请求或操作会对数据产生影响(比如新增、删除、更新),针对这类请求一般都需要做一些保护,以防止用户有意或无意的重复发起这样的请求导致的数据错乱。
本文总结了一些防止客户端重复发送请求的方法。
方法一:JS监听Form的onsubmit事件
在经典场景下,浏览器通过Form发送请求。因此只需要在Form onsubmit时将Submit按钮disable,就能够防止用户双击导致的重复请求(这种问题一般发生在年纪大的用户身上,他们分不清单击和双击)。
但是随着前端的发展,Form以外的请求方式也越来越多,比如利用各种前端框架(Vue、AngularJs、Backbone等)写的App,他们更多的采用的是ajax的方式和后端交互。那么前端开发人员必须在开发时针对每个代表发起请求的UI元素做处理,像Form一样,在发起请求的时候把相关UI元素禁用掉。
而有些交互方式则可能连代表发起请求的UI元素都没有,比如Segmentfault的markdown编辑器就是在一边输入的时候一边保存的。那么这时就需要前端代码采用其他手段来控制重复请求的发生。
优点:
- 不需要后端写代码
缺点:
- 不存在统一的解决方案,必须针对每种情况写处理代码
- 无法控制浏览器刷新发起的重复请求
- 前端开发人员忘记写相关代码
- 无法控制恶意的重复请求,比如绕过浏览器直接发起
方法二:Http Status Code 302(后端重定向)
服务端采用重定向的方式,防止用户刷新浏览器发出重复请求。这是比较经典的后端控制重复请求的方式,因为一旦重定向成功后,用户刷新浏览器所刷新的是那个重定向地址,而不是数据操作地址。
优点:
- 不需要写前端代码
缺点:
- 在还未响应302之前,所发起的重复请求,比如:用户快速的双击、刷新浏览器
- 在某些前端程序里(比如SPA),不能使用重定向
- 后端开发人员忘记写相关代码
- 无法控制恶意的重复请求,比如绕过浏览器直接发起
方法三:结合方法一和方法二
结合方法一和方法二的话倒是可以解决大部分问题,但是解决不了以下问题:
- 在还未响应302之前,用户刷新浏览器导致的重复请求
- 有些场景下压根不能使用重定向
- 前、后端开发人员忘记写相关代码
- 无法控制恶意的重复请求,比如绕过浏览器直接发起
方法四:token方式
token的流程是这样的:
- 在浏览器发送请求前,先到服务端索要token
- 浏览器发送请求时,将token一并提交
- 服务端检查请求是否携带token、token是否有效(比如是否正确、是否过期)。如果不正确则响应失败;如果正确则销毁token,继续业务逻辑。
关键点在于:
- 每个token都是一次性且有过期时间的,能够防止token前端代码bug造成的重复利用和无限利用。
- 服务器要求请求必须携带token,能够避免前端开发人员漏写相关代码。
那么token是以怎样的形式传输的呢?我认为有以下两种方式:
Cookie:
推荐使用这种方式,因为浏览器每次都会将cookie携带在请求里一并发出,所以前端发送请求的代码都不需要修改,只要在发送请求前问服务器拿token就行了。
比如在进入Form页面时,服务器将token以cookie的形式一并携带在响应中,那么前端Form提交时,就会将cookie一并携带在请求中,前端的代码一点都不需要修改。
json:
前端发起ajax请求像后端拿token,后端以json的形式返回token,前端发送请求时将token携带在请求中,后端检验。
这种方式比Cookie稍微麻烦的地方是,前端必须写一些代码来保存这个token,然后在发送请求的地方要写一些代码把token携带在请求里。
优点:
- 前端代码可以写的少一些,比如禁用UI元素的代码可以不写
- 能够解决在还未响应302之前,用户刷新浏览器导致的重复请求
- 适应有些场景下压根不能使用重定向
缺点:
- 前、后端开发人员忘记写相关代码。这个真的解决不了。
- 无法控制通过脚本运行的,具有整套流程的恶意请求。这种请求在程序看来完全合法,但却属于恶意行为,针对这类恶意行为的防控属于另一个话题,本人不懂,所以在这里就不多讲了。
方法五:利用数据库的唯一约束
如果请求会insert数据,而这个数据正好存在业务主键,那么可以利用数据库的唯一约束来做进一步的防御。
方法六:请求幂等化
有些业务情形下,请求是幂等的,这就意味着可以不用为重复发生请求而烦恼了——至少在业务逻辑层面不用烦恼了。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。