java 表单避免重复提交

问题

就是同一份信息,重复的提交给服务器。

场景

  1. 点击F5刷新页面: 当用户点击submit将已经写好的表单数据提交到服务器时,可以在浏览器的url看到地址和参数的变化,但因为网速等问题,用户当前页面并未刷新,或者点击刷新页面,造成表单重复提交。

  2. 重复点击提交按钮: 因为网络问题,未能及时跳转显示内容,部分用户可能会出于心急重复提交提交按钮,造成多次提交内容到服务器。

  3. 前进后退操作 :有些用户在进行某些工作操作时,可能出于需要或者某种情况,进行后退操作,浏览刚才填入的信息,在进行后退和前进的操作可能也会造成表单数据重复提交到服务器。

  4. 使用浏览器历史记录重复访问: 某些用户可能会出于好奇,利用浏览器的历史记录功能重复访问提交页面,同样会造成表单重复提交问题。

解决思路

前端

方案一:禁用按钮提交

ps: 以前写前端就用过这种方式。

设置标志位,提交之后禁止按钮。

  • 优点

简单。基本可以防止重复点击提交按钮造成的重复提交问题。

  • 缺陷

前进后退操作,或者F5刷新页面等问题并不能得到解决。

最重要的一点,前端的代码只能防止不懂js的用户,如果碰到懂得js的编程人员,那js方法就没用了。

方案二:设置HTTP报头、

设置HTTP报头,控制表单缓存,使得所控制的表单不缓存信息,这样用户就无法通过重复点击按钮去重复提交表单。

<meta http-equiv="Cache-Control" content="no-cache, must-revalidate">

但是这样做也有局限性,用户在提交页面点击刷新也会造成表单的重复提交。

方案三:通过 PRG 设计模式

用来防止F5刷新重复提交表单。

PRG模式通过响应页面Header返回HTTP状态码进行页面跳转替代响应页面跳转过程。

具体过程如下:

客户端用POST方法请求服务器端数据变更,服务器对客户端发来的请求进行处理重定向到另一个结果页面上,客户端所有对页面的显示请求都用get方法告知服务器端,这样做,后退再前进或刷新的行为都发出的是get请求,不会对server产生任何数据更改的影响。

但此方法也不能防止所有情况: 例如用户多次点击提交按钮;恶意用户避开客户端预防多次提交手段,进行重复提交请求;

下面谈一谈后端的防止重复提交。

后端

幂等性

如果是注册或存入数据库的操作,可以通过在数据库中字段设立唯一标识来解决,这样在进行数据库插入操作时,因为每次插入的数据都相同,数据库会拒绝写入。

这样也避免了向数据库中写入垃圾数据的情况,同时也解决了表单重复提交问题。

但是这种方法在业务逻辑上感觉是说不过去的,本来该有的逻辑,却因为数据库该有的设计隐藏了。

而且这种方法也有一定的功能局限性,只适用于某系特定的插入操作。

  • 实现方式

这种操作,都需要有一个唯一标识。数据库中做唯一索引约束,重复插入直接报错。

  • 缺点

有很大的约束性。

session 方法

Java 使用Token令牌防止表单重复提交的步骤:

  1. 在服务器端生成一个唯一的随机标识号,专业术语称为Token(令牌),同时在当前用户的Session域中保存这个Token。

  2. 将Token发送到客户端的Form表单中,在Form表单中使用隐藏域来存储这个Token,表单提交的时候连同这个Token一起提交到服务器端。

  3. 在服务器端判断客户端提交上来的Token与服务器端生成的Token是否一致,如果不一致,那就是重复提交了,此时服务器端就可以不处理重复提交的表单。如果相同则处理表单提交,处理完后清除当前用户的Session域中存储的标识号。

  • 下面的场景将拒绝处理用户提交的表单请求
  1. 存储Session域中的Token(令牌)与表单提交的Token(令牌)不同。

  2. 当前用户的Session中不存在Token(令牌)。

  • 实现方式

aop 拦截器。

个人理解

前后端结合,后端保证安全性。前端减轻后端的压力。

和参数校验是一个道理。

为什么要设置一个隐藏域?

这个问题我一开始没想明白,我认为,进入页面的时候设置一个session并且再token设值,添加的时候把这个值删掉。然后这样我们再按F5的时候就没办法重复提交了(因为这个时候判断session为空)。我觉得这样就ok了,设置hidden域感觉没任何必要。

然而简直是图样图森破,对于一般用户这样当然是可以的。

但是对于恶意用户呢?假如恶意用户开两个浏览器窗口(同一浏览器的窗口公用一个session)这样窗口1提交完,系统删掉session,窗口1停留着,他打开第二个窗口进入这个页面,系统又为他们添加了一个session,这个时候窗口1按下F5,那么直接重复提交!

所以,我们必须得用hidden隐藏一个uuid的token,并且在后台比较它是否与session中的值一致,只有这样才能保证F5是不可能被重复提交的!

参考资料

  • 重复提交

Java 使用Token令牌防止表单重复提交

有效防止F5刷新重复提交

  • in action

JAVA-防止重复提交-过滤器