一、场景
1、由于用户误操作,多次点击表单提交按钮。
2、由于网速等原因造成页面卡顿,用户重复刷新提交页面。
3、黑客或恶意用户使用postman等工具重复恶意提交表单(攻击网站)。
二、解决方案
1、通过JavaScript屏蔽提交按钮(不推荐)
通过js代码,当用户点击提交按钮后,屏蔽提交按钮使用户无法点击提交按钮或点击无效,从而实现防止表单重复提交。
缺点:js代码很容易被绕过。比如用户通过刷新页面方式,或使用postman等工具绕过前段页面仍能重复提交表单。因此不推荐此方法。
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%> <!DOCTYPE HTML> <html> <head> <title>表单</title> <script type="text/javascript"> //默认提交状态为false var commitStatus = false; function dosubmit(){ if(commitStatus==false){ //提交表单后,讲提交状态改为true commitStatus = true; return true; }else{ return false; } } </script> </head> <body> <form action="/path/post" onsubmit="return dosubmit()" method="post"> 用户名:<input type="text" name="username"> <input type="submit" value="提交" id="submit"> </form> </body> </html>
2、给数据库增加唯一键约束(简单粗暴)
在数据库建表的时候在ID字段添加主键约束,用户名、邮箱、电话等字段加唯一性约束。确保数据库只可以添加一条数据。
-- 数据库加唯一性约束sql: alter table tableName_xxx add unique key uniq_xxx(field1, field2)
缺点:通过数据库加唯一键约束能有效避免数据库重复插入相同数据。但无法阻止恶意用户重复提交表单(攻击网站),服务器大量执行sql插入语句,增加服务器和数据库负荷。
3、利用Session防止表单重复提交(推荐)
1】实现原理:
服务器返回表单页面时,会先生成一个token保存于session,并把该token传给表单页面。当表单提交时会带上token,服务器拦截器Interceptor会拦截该请求,拦截器判断session保存的token和表单提交token是否一致。若不一致或session的token为空或表单未携带token则不通过。
首次提交表单时session的token与表单携带的token一致走正常流程,然后拦截器内会删除session保存的token。当再次提交表单时由于session的token为空则不通过。从而实现了防止表单重复提交。
2】Struts2实现
// 1、要防止重复提交的jsp页面: <s:token /><!-- 生成令牌号,防止表单重复提交 --> // 2、struts.xml <package name="default" namespace="/" extends="struts-default"> <action name="regist" class="cn.itcast.action.RegistAction"> <result name="invalid.token">/token.jsp</result><!-- name必须是: invalid.token --> <interceptor-ref name="token" /> <interceptor-ref name="defaultStack" /> </action> </package> // 3、token.jsp 您已经重复提交表单,请刷新后重试! <br>
3】session实现
// jsp页面 <% //生成一个令牌 String token=UUID.randomUUID().toString(); session.setAttribute("token", token); %> <form action="${pageContext.request.contextPath}/regist" method="get"> <input type="hidden" name="token" value="<%=token%>"> username:<input type="text" name="username"><br> password:<input type="password" name="password"><br> <input type="submit" value="注册"> </form> // RegistServlet.java public class RegistServlet extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html;charset=utf-8"); String username = request.getParameter("username"); String password = request.getParameter("password"); String token = request.getParameter("token"); String _token = (String) request.getSession().getAttribute("token"); request.getSession().removeAttribute("token"); if (token.equals(_token)) { System.out.println("将" + username + " 与" + password + "存储到数据库中,完成注册"); } else { response.getWriter().write("不允许表单重复提交"); } } public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } }