防止form表单重复提交的X种方法

  • Post author:
  • Post category:其他


Form表单重复提交是在多用户Web应用中最常见、带来很多麻烦的一个问题。有很多的应用场景都会遇到重复提交问题,比如:

(1)点击提交按钮两次。

(2)点击刷新按钮。

(3)使用浏览器后退按钮重复之前的操作,导致重复提交表单。

(4)使用浏览器历史记录重复提交表单。

(5)浏览器重复的HTTP请求。

(6)用户提交表单时可能因为网速的原因,或者网页被恶意刷新,致使同一条记录重复插入到数据库中,这是一个比较棘手的问题。我们可以从客户端和服务器端一起着手,设法避免同一表单的重复提交。

参考网友建议和自己的体会,收集了以下几种有效防止表单重复提交的方法:


一、用Javascript禁掉提交按钮

表单提交之后,用JS置灰submit按钮,可防止心急用户重复点击提交按钮。

示例代码如下:

<script type="text/javascript">
    //设置disabed属性
    $("input[type='submit']").attr("disabled",true);
    或者 $("input[type='submit']").attr("disabled","disabled");
    
    //移除disabed属性
    $("input[type='submit']").attr("disabled",false);
    或者 $("input[type='submit']").attr("disabled","");
</script>

如果客户端禁用JS,这种方法就会失效。


二、在数据库里添加唯一约束

在数据库里添加唯一约束或创建唯一索引,防止出现重复数据。这是最有效的防止重复提交数据的方法。

//创建表时创建唯一性约束

//在mysql中添加唯一约束:
CREATE TABLE `T_User` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(18) NOT NULL unique,
  `password` varchar(18) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1018 DEFAULT CHARSET=gbk;


//在oracle中添加唯一约束:
create table `T_User`
(
  `id` number not null,
  `username`  varchar2(50),
  `password`  varchar2(50),
  CONSTRAINT tb_user_u1 UNIQUE (`id`)
);


三、用Post-Redirect-Get(PRG)模式

1、用sendRedirect()函数转向

用户提交表单之后,执行重定向,转到成功信息页面。可避免用户按F5刷新页面和点击浏览器前进或后退导致的重复提交。

示例代码如下:

public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException,IOException{
  response.setContentType("text/html; charset=utf-8");
  response.sendRedirect("/success.jsp");
}

2、用forward()函数转向

除此之外,当用户提交表单,服务器端调用forward()方法,转发到其他页面。

示例代码如下:

public void doPost(HttpServletRequest request, HttpServletResponse response) 
throws ServletException,IOException{
  response.setContentType("text/html; charset=utf-8");
  ServletContext sc = getServletContext();
  sc.getRequestDispatcher("/success.jsp").forward(request, response);
}


四、用Session防止表单重复提交

具体的做法:在服务器端生成一个唯一的随机标识号,专业术语称为Token(令牌),同时在当前用户的Session域中保存这个Token。然后将Token发送到客户端的Form表单中,在Form表单中使用隐藏域来存储这个Token,表单提交的时候连同这个Token一起提交到服务器端,然后在服务器端判断客户端提交上来的Token与服务器端生成的Token是否一致,如果不一致,那就是重复提交了,此时服务器端就可以不处理重复提交的表单。如果相同则处理表单提交,处理完后清除当前用户的Session域中存储的标识号。

在下列情况下,服务器程序将拒绝处理用户提交的表单请求:

(1)存储Session域中的Token(令牌)与表单提交的Token(令牌)不同。

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

(3)用户提交的表单数据中没有Token(令牌)。

示例代码如下:

1、创建FormServlet,用于生成Token(令牌)和跳转到form.jsp页面

public class FormServlet extends HttpServlet {
    private static final long serialVersionUID = -884689940866074733L;

    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        String token = UUID.randomUUID().toString();//创建令牌
        System.out.println("在FormServlet中生成的token:"+token);
        request.getSession().setAttribute("token", token);  //在服务器使用session保存token(令牌)
        request.getRequestDispatcher("/form.jsp").forward(request, response);//跳转到form.jsp页面
    }

    public void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        doGet(request, response);
    }

}

2、在form.jsp中使用隐藏域来存储

Token(令牌)

<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>form表单</title>
</head>

<body>
    <form action="${pageContext.request.contextPath}/servlet/DoFormServlet" method="post">
        <%--使用隐藏域存储生成的token--%>
        <%--
            <input type="hidden" name="token" value="<%=session.getAttribute("token") %>">
        --%>
        <%--使用EL表达式取出存储在session中的token--%>
        <input type="hidden" name="token" value="${token}"/> 
        用户名:<input type="text" name="username"> 
        <input type="submit" value="提交">
    </form>
</body>
</html>

3、DoFormServlet处理表单提交

public class DoFormServlet extends HttpServlet {

    public void doGet(HttpServletRequest request, HttpServletResponse response)
                throws ServletException, IOException {

            boolean b = isRepeatSubmit(request);//判断用户是否是重复提交
            if(b==true){
                System.out.println("请不要重复提交");
                return;
            }
            request.getSession().removeAttribute("token");//移除session中的token
            System.out.println("处理用户提交请求!!");
        }
        
        /**
         * 判断客户端提交上来的令牌和服务器端生成的令牌是否一致
         * @param request
         * @return 
         *         true 用户重复提交了表单 
         *         false 用户没有重复提交表单
         */
        private boolean isRepeatSubmit(HttpServletRequest request) {
            String client_token = request.getParameter("token");
            //1、如果用户提交的表单数据中没有token,则用户是重复提交了表单
            if(client_token==null){
                return true;
            }
            //取出存储在Session中的token
            String server_token = (String) request.getSession().getAttribute("token");
            //2、如果当前用户的Session中不存在Token(令牌),则用户是重复提交了表单
            if(server_token==null){
                return true;
            }
            //3、存储在Session中的Token(令牌)与表单提交的Token(令牌)不同,则用户是重复提交了表单
            if(!client_token.equals(server_token)){
                return true;
            }
            
            return false;
        }

    public void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        doGet(request, response);
    }

}

详情参考:

http://www.cnblogs.com/xdp-gacl/p/3859416.html


五、用Struts2的token机制

Struts2中的token标签可以用来生成一个唯一的标记,这个标记必须嵌套在form标签内使用,它将在form表单里插入一个隐藏字段并把标记保存到Session中。

示例代码如下:

1、在struts.xml里面需要进行如下配置:

<packagename="default"namespace="/"extends="struts-default">
    <action name="submit"class="action.SubmitAction">
        <result name="success">/success.jsp</result>
        <result name="invalid.token">/input.jsp</result>
         
        <interceptor-ref name="tokenSession"></interceptor-ref>
        <interceptor-ref name="defaultStack"></interceptor-ref>
    </action>
</package>

2、在需要提交的jsp页面加上<s:token></s:token>标签 (要使用token机制,必须使用struts2提供的标签库)

<form action="submit.action" method="post">
      name : <input type="text" name="name"/><br/>
      password : <input type="text" name=password/><br>
      <s:token></s:token>
      <input type="submit" value="submit"/>
</form>

详情参考:

https://blog.csdn.net/jdluojing/article/details/7611254



六、用Cookie做处理

以User(假设有一个user类)举例说明,将用户id和”ok” + id分别放到cookie里面,根据需要设置cookie存活时间,然后放到response里面。在每次提交form表单时,先判断cookie中的name是否是已经提交过的表单名称,如果是就重定向到error页面。

示例代码如下:

Cookie[] cookies = request.getCookies();
for (Cookie cookie : cookies) {
    if (String.valueOf(user.getId()).equals(cookie.getValue())) {
        response.sendRedirect("error.jsp");
    } else {
	Cookie cookie2 = new Cookie("ok" + user.getId(), String.valueOf(user.getId()));
	response.addCookie(cookie2);
    }
}



版权声明:本文为Huozhiwu_11原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。