令牌机制,防止表单重复提交

方法一:利用struts2中的token拦截器

struts2中有一个token拦截器,但不是默认拦截器栈中的一个,所以此处用token拦截器来阻止表单重复提交

创建表单jsp页面:

 1 <%@ page language="java" contentType="text/html; charset=UTF-8"
 2     pageEncoding="UTF-8"%>
 3 <%@ taglib uri="/struts-tags" prefix="s"%>  <!-- 引入struts2标签库 -->
 4 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
 5 <html>
 6 <head>
 7 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
 8 <title>My First Struts2 Project</title>
 9 </head>
10 <body>
11 <form action="${pageContext.request.contextPath }/stopRepeat/login_login.action" method="post">
12     <s:token/><!-- 添加防止重复提交标签 -->
13     用户名<input type="text" name="name"><br>
14     手机号<input type="text" name="mobile"><br>
15     <input type="submit" value="登录">
16 </form>
17 </body>
18 </html>

Action方法代码:(Action类要继承ActionSupport类)

 1 package com.bjyinfu.struts.actions;
 2 
 3 
 4 import com.opensymphony.xwork2.ActionSupport;
 5 
 6 public class LoginAction6 extends ActionSupport {
 7 
 8     private String mobile;
 9     private String name;
10     public String getMobile() {
11         return mobile;
12     }
13     public void setMobile(String mobile) {
14         this.mobile = mobile;
15     }
16     public String getName() {
17         return name;
18     }
19     public void setName(String name) {
20         this.name = name;
21     }
22     public String login(){
23         return "success";
24     }
25 }

注册Action方法:

令牌机制,防止表单重复提交

重复提交报错视图:

 1 <%@ page language="java" contentType="text/html; charset=UTF-8"
 2     pageEncoding="UTF-8"%>
 3 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
 4 <html>
 5 <head>
 6 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
 7 <title>My First Struts2 Project</title>
 8 </head>
 9 <body>
10 <h1>请求正在处理!请稍后。。。。。。</h1>
11 </body>
12 </html>

引入struts2标签库,在表单内部添加令牌标签<s:token/>,当浏览器访问此jsp页面时,服务器会知道此jsp中有表单,并且表单进行了防止提交,此时服务器会生成令牌给表单一份,服务器自己留一份与之对应的令牌,第一次访问此表单时令牌对应的,此时服务器发现令牌对应后,会立即将自己手中的令牌进行修改,而表单中的令牌内容没有变化,所以当由于网络原因服务器未进行回应的时候再次提交表单,此时表单中的令牌已经与服务器中的令牌不对应,服务器会抛出异常自动转跳到"invalid.token"视图上,而不会将表单再次提交,这就是struts2的token令牌机制;而抛出的异常转跳可以注册到struts.xml中对应的Action方法中来实现程序的健壮;

重复提交时:令牌机制,防止表单重复提交

回退到表单中:令牌机制,防止表单重复提交

 方法二:手动编写令牌机制,利用session

手工方法一:该方法原理是 记住上一次提交的 页面token。将本次的token和上次比对,如果一样说明重复提交。该方法不需要禁用缓存。浏览器回退再次进行提交,必须进行刷新界面才能生效

jsp代码:

 1 <%@ page language="java" contentType="text/html; charset=UTF-8"
 2     pageEncoding="UTF-8"%>
 3 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
 4 <html>
 5 <head>
 6 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
 7 <title>Insert title here</title>
 8 </head>
 9 <body>
10 <%  
11            long token=System.currentTimeMillis();    //产生时间戳的token放到session中
12             session.setAttribute("token",token);      
13     %>  
14 
15     <form  action="${pageContext.servletContext.contextPath }/formSubmit/submit_.action" method="post">  
16         <input type="text"  name="username"/>  <br>
17         <input type="text"  name="password"/>  <br>  
18         <input type="hidden" value="${token }" name="reqtoken"/>  <br>   <!-- 作为hidden提交 -->  
19         <input type="submit" value="提交"/>  <br>  
20     </form>  
21 </body>
22 </html>

action代码

 1 package com.bjyinfu.struts.actions;
 2 
 3 import java.util.Map;
 4 
 5 import org.apache.struts2.ServletActionContext;
 6 
 7 public class FormSubmit {
 8 
 9     private String username;
10     private String password;
11     private String reqtoken;
12     
13     public String getUsername() {
14         return username;
15     }
16     public void setUsername(String username) {
17         this.username = username;
18     }
19     public String getPassword() {
20         return password;
21     }
22     public void setPassword(String password) {
23         this.password = password;
24     }
25     public String getReqtoken() {
26         return reqtoken;
27     }
28     public void setReqtoken(String reqtoken) {
29         this.reqtoken = reqtoken;
30     }
31     public String execute(){
32         //利用ServletActionContext获取session对象
33         Map<String, Object> session = ServletActionContext.getContext().getSession();
34         //从session中获取上一次提交表单时,session中的令牌
35         Long lastTokenInSession =  (Long) session.get("lastToken");
36         Long tokenInForm=Long.parseLong(reqtoken);
37         /**
38          * 长整型不能用双等号进行比较
39          * tokenInForm!=null 是防止用户直接打开本servlet页面。
40          * LasttokenInSession是空,说明是第一次提交 ,和上一次不想等则说明不是重复提交 
41          */
42         if(tokenInForm!=null && (lastTokenInSession==null || !lastTokenInSession.equals(tokenInForm))){
43             //第一次提交和多次非重复提交
44             session.remove("token");
45             //将表单中的新令牌放到session中
46             session.put("lastToken", tokenInForm);
47             return "success";
48         }else{
49             return "fail";
50         }
51     }
52 }

手工方法二:第一次提交之后,判断这两个token是否一样,是一样则通过,并且清除session 中的 token,这样就能防止返回之后再次提交,因为返回的时候点击提交读取的是缓存,但是session已经没有这个token了。

jsp代码:

 1 package com.bjyinfu.struts.actions;
 2 
 3 import java.util.Map;
 4 
 5 import org.apache.struts2.ServletActionContext;
 6 
 7 public class FormSubmit {
 8 
 9     private String username;
10     private String password;
11     private String reqtoken;
12     
13     public String getUsername() {
14         return username;
15     }
16     public void setUsername(String username) {
17         this.username = username;
18     }
19     public String getPassword() {
20         return password;
21     }
22     public void setPassword(String password) {
23         this.password = password;
24     }
25     public String getReqtoken() {
26         return reqtoken;
27     }
28     public void setReqtoken(String reqtoken) {
29         this.reqtoken = reqtoken;
30     }
31     public String execute(){
32         //利用ServletActionContext获取session对象
33         Map<String, Object> session = ServletActionContext.getContext().getSession();
34         //获取session中的令牌
35         Long TokenInSession =  (Long) session.get("token");
36         //获取表单中的令牌
37         Long tokenInForm=Long.parseLong(reqtoken);
38         /**
39          * 表单中的令牌和session中的令牌不能为空,并且session中的令牌要与表单中的令牌相同才能正常提交
40          * 第一次提交完之后再次刷新,也不会实现二次提交,
41          * 浏览器回退到表单页后再次提交,也不会实现二次提交(回退提交可以先清空浏览器缓存,这样就可以让用户回退去修改信息,实现正常提交了)
42          */
43         if(tokenInForm!=null && TokenInSession!=null && TokenInSession.equals(tokenInForm)){
44             //第一次提交和多次非重复提交
45             session.remove("token");
46             return "success";
47         }else{
48             System.out.println("返回并刷新界面");
49             return "fail";
50         }
51     }
52 }

在表单页实现清空浏览器缓存,可参考此随笔