平时网上注册大家都经历过,比如用户在注册成功之后,再打击后退按钮,退回到表单页再次提交表单,如果未处理重复提交这一细节上的要求,将会再次成功提交,数据库中有重复数据,在一个良好的程序中式不允许这么出现的。
Struts的Token(令牌)机制能够很好的解决表单重复提交的问题。
基本原理是:服务器在处理到达的请求之前,会将请求中包含令牌值与保存在当前会话中的令牌值进行比较,看是否匹配。在处理完该请求后,且在相应发送给客户端之前,将会产生一个新的令牌值,该令牌值除传给客户端以外,也会将用户会话中的令牌值进行替换,这样如果用户后退到刚才的提交页面并再次提交的话,客户端传过来的令牌就和服务器端的令牌不一致,从而有效的防止了重复提交的发生
用户在使用时要注意一下两点:
第一、用户需要在请求中包含这个令牌值,请求中的令牌值如何保存,其实就和平时在页面中保存一些信息是一样的,通过隐藏域来保存,保存的形式如:<input type="hidden" name="org.apache.struts.taglib.html.TOKEN" value="6aa35341f25184fd996c4c918255c3ae" >,这个value是TokenProcessor类中的generateToken()获得的,是根据当前用户的session id 和当前时间的long值计算的
第二、在客户端提交后,要判断在请求中包含的值是否和服务器的令牌一直,因为服务器每次提交都会生成新的Token,所以,如果是重复提交,客户端的Token值和服务器端的Token值就会不一致
示例
index.jsp
- <span style="font-size: large;"><%@ page pageEncoding="UTF-8"%>
- <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
- <html>
- <head>
- <title>Struts应用:利用Token防止重复提交</title>
- </head>
- <body>
- <h3>利用Token防止重复提交</h3><hr/>
- <a href="user.do?method=toAdd">添加用户</a>
- </body>
- </html></span>
addUser.jsp
- <span style="font-size: large;"><%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
- <%@ taglib uri="http://struts.apache.org/tags-html" prefix="html" %>
- <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
- <html>
- <head>
- <title>添加用户</title>
- <meta http-equiv="pragma" content="no-cache">
- <meta http-equiv="cache-control" content="no-cache">
- <meta http-equiv="expires" content="0">
- <meta http-equiv="keywords" content="keyword1,keyword2,keyword3">
- <meta http-equiv="description" content="This is my page">
- <!--
- <link rel="stylesheet" type="text/css" href="styles.css">
- -->
- </head>
- <body>
- <h3>添加用户</h3>
- <html:form action="user.do?method=add" method="post">
- <table border="1" width="600px">
- <tr>
- <td>登录名</td>
- <td><input type="text" name="loginname"/></td>
- </tr>
- <tr>
- <td>密码</td>
- <td><input type="password" name="pwd"/></td>
- </tr>
- <tr>
- <td colspan="2" align="center">
- <input type="submit" value="提交"/>
- <input type="reset" value="重置"/>
- </td>
- </tr>
- </table>
- </html:form>
- </body>
- </html>
- </span>
login_success.jsp
- <span style="font-size: large;"><%@ page pageEncoding="UTF-8"%>
- <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
- <html>
- <head>
- <title>登录成功</title>
- </head>
- <body>
- <h3>登录成功</h3><hr/>
- <h2>欢迎:${param.loginname}登录!</h2>
- </body>
- </html></span>
login_failure.jsp
- <span style="font-size: large;"><%@ page pageEncoding="UTF-8"%>
- <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
- <html>
- <head>
- <title>登录失败</title>
- </head>
- <body>
- <h3>登录失败</h3><hr/>
- <h2 style="color:red">可能的原因是:${errorMsg}</h2>
- </body>
- </html></span>
LoginForm.java
- <span style="font-size: large;">package com.javacrazyer.web.formbean;
- import javax.servlet.http.HttpServletRequest;
- import org.apache.struts.action.ActionErrors;
- import org.apache.struts.action.ActionForm;
- import org.apache.struts.action.ActionMapping;
- /**
- * 用来收集客户端提交数据.
- * 要收集数据的属性的名一定要跟请求参数名相同
- */
- public class LoginForm extends ActionForm {
- public ActionErrors validate(ActionMapping mapping,
- HttpServletRequest request) {
- return null;
- }
- private static final long serialVersionUID = 6619272689058619128L;
- private String loginname;
- private String pwd;
- public String getLoginname() {
- return loginname;
- }
- public void setLoginname(String loginname) {
- this.loginname = loginname;
- }
- public String getPwd() {
- return pwd;
- }
- public void setPwd(String pwd) {
- this.pwd = pwd;
- }
- }</span>
LoginAction.java
- <span style="font-size: large;">package com.javacrazyer.web.action;
- import javax.servlet.http.HttpServletRequest;
- import javax.servlet.http.HttpServletResponse;
- import org.apache.struts.action.ActionForm;
- import org.apache.struts.action.ActionForward;
- import org.apache.struts.action.ActionMapping;
- import org.apache.struts.actions.DispatchAction;
- /**
- * 业务控制器:控制业务处理的流程
- *
- */
- public class LoginAction extends DispatchAction {
- //具体业务流程处理方法,由Struts框架回调
- public ActionForward toAdd(ActionMapping mapping, ActionForm form,
- HttpServletRequest request, HttpServletResponse response)
- throws Exception {
- //产生Token值并存储到session中
- this.saveToken(request);
- return mapping.findForward("toadd");
- }
- public ActionForward add(ActionMapping mapping, ActionForm form,
- HttpServletRequest request, HttpServletResponse response)
- throws Exception {
- if(this.isTokenValid(request)){
- this.saveToken(request);
- System.out.println("往数据库添加数据...");
- return mapping.findForward("succ");
- }else{
- System.out.println("请不要重复提交数据.....");
- return mapping.findForward("failure");
- }
- }
- }</span>
WEB-INF/struts-config.xml
- <span style="font-size: large;"><?xml version="1.0" encoding="UTF-8" ?>
- <!DOCTYPE struts-config PUBLIC
- "-//Apache Software Foundation//DTD Struts Configuration 1.2//EN"
- "http://jakarta.apache.org/struts/dtds/struts-config_1_2.dtd">
- <struts-config>
- <!-- ================================================ Form Bean Definitions
- -->
- <form-beans>
- <form-bean name="loginForm" type="com.javacrazyer.web.formbean.LoginForm"/>
- </form-beans>
- <!-- =========================================== Action Mapping Definitions
- -->
- <action-mappings>
- <action path="/user" name="loginForm" type="com.javacrazyer.web.action.LoginAction"
- parameter="method">
- <forward name="toadd" path="/adduser.jsp"/>
- <forward name="succ" path="/login_success.jsp"/>
- <forward name="failure" path="/login_failure.jsp"/>
- </action>
- </action-mappings>
- </struts-config></span>
具体页面的流程为
这时如果点击后退那个箭头,再提交的话那么就是重复提交了,控制台多出现一条‘请不要重复提交数据’的输出信息
解释下这个流程:
首先是点击添加用户,跳到登录页面,这个过程调用了LoginAction.java中的toadd方法,这个方法中正好有个
saveToken(request)方法,那么这就说明了要将这步请求的TOKEN值保存在会话中,这样跳到登陆页面的话,登录页面会多出一个隐藏表单类似于上面说的<input type="hidden" name="org.apache.struts.taglib.html.TOKEN" value="6aa35341f25184fd996c4c918255c3ae" >,点击登录后那么就会出现将登录页面请求中包含的TOKEN值跟会话中的TOKEN值对比的情况,如果通过就提交成功。这样处理过后那么就会产生新的令牌值,会话中的令牌值也将改变,因此当点击最后页面的后退在提交时,页面的TOKEN值与会话中的已经不一样了,所以就避免了重复提交