jsp中防止刷新后退等操作造成表单重复提交(纯jsp或者struts)

来源:互联网 发布:搬家软件 编辑:程序博客网 时间:2024/05/21 07:47

 1        js验证方法(js 判断防止form重新提交)

 1.1 在页面中设置一个隐藏域值判断

             <input   type="hidden"   value="false"  name="server"/>  
              如果已经POST   OK   过,   就改变   value="true"   ;   
              再次POST   时候,可以用   JS   在客户端判断   hidden   ,   也可以在SERVER   检查.   

 1.2 在页面中设置一个变量判断

<script language="javascript">

   var checkSubmitFlg = false;

   function checkSubmit() {

   if (checkSubmitFlg == true) {

   return false;

   }

   checkSubmitFlg = true;

   return true;

   }

   document.ondblclick = function docondblclick() {

   window.event.returnValue = false;

   }

   document.onclick = function doconclick() {

   if (checkSubmitFlg) {

   window.event.returnValue = false;

   }

   }

  </script>

<html:form action="myAction.do" method="post" onsubmit="return checkSubmit();">

 1.3 将提交按钮或者image置为disable  

如 <html:form action="myAction.do" method="post"

   onsubmit="getElById('submitInput').disabled = true; return true;">   

   <html:image styleId="submitInput" src="images/ok_b.gif" border="0" /> 

   </html:form>  

 

<input type="button" value="提交" onclick="this.disabled=true;this.form.submit()">

2  清空缓存防止提交

<META         HTTP-EQUIV="pragma "         CONTENT="no-cache ">             

<META         HTTP-EQUIV= "Cache-Control "         CONTENT="no-cache,         must-revalidate ">             

<META         HTTP-EQUIV= "expires "         CONTENT= "Wed,         26         Feb         1997         08:21:57         GMT ">    

 

3 session令牌方式

生成一个令牌保存在用户session中,在form中加一个hidden域,显示该令牌的值,form提交后重新生成一个新的令牌,将用户提交的令牌和session中的令牌比较,如相同则是重复提交

说简单点,防止重复提交的原理就是在服务端和在客户端同时存在一个Token,当客户端发送一个request到服务端的时候,包含这个token的信息,当然,这个请求同样会包含当前的sessionID,
当请求到达服务端的时候,服务端用过sessionID去得到当前的session,并且在该session中查找改Token,并且与request中所包含的Token进行比较,如果相同责为有效请求,在请求有效之前从session将该Token删除,这样,如果重复提交,则为无效请求.

用一段代码来表示吧.
首先定义两个变量

public static String TOKEN_FOR_SESSION="token.for.session";
public static String TOKEN_FOR_REQUEST="token.for.request";

 

考虑add这个方法需要防止重复提交,因此在add发出的这个request的时候,一定需要包含这个TOKEN,
因此,需要增加一个toAdd这样一个方法,在这个方法中,转向add这个页面,而toAdd这个方法中的概要代码:
String token=getToken();
request.setAttribute(TOKEN_FOR_REQUIRED,token);
session.setAttribute(TOKEN_FOR_SESSION,token);
request.getRequestDispatcher("/add.jsp").forward(request,response);

在add.jsp中包含如下信息:
<input type="hidden" value="${token.for.request}"/>

在add的action处理中:

String savedToken=request.getSession().getAttribute(TOKEN_FOR_SESSION).toString();
String requestToken=request.getParameter(TOKEN_FOR_REQUEST);

request.getSession().removeAttribute(TOKEN_FOR_SESSION);
if(savedToken.equals(requestToken))
{
    //TODO:your own business logic.
}

这个就是防止重复提交的概要思想.


4  redirect=true 或者 request.redirect();

重定向可以解决页面刷新带来的数据的重复提交的问题,我们自然可以利用重定向的方式来解决这个问题。但是struts的action里面mapping.findword()

 修改struts-config.xml 文件,在action里面有一个redirect重新定向的属性,struts中默认的是false,添加这个属性,改成true,在forword中写上要跳转页面的绝对或者相对地址就行了

修改如下:

<action-mappings

  <action attribute="newsActionForm" name="newsActionForm"

   input="/addnews.jsp" path="/newsAction" parameter="method"

   scope="request" type="com.yongtree.news.action.NewsAction">

   <forward name="list" path="/listnews.jsp" redirect="true"></forward>

   <forward name="error" path="/addnews.jsp"></forward>

  </action>

</action-mappings> 

但是因为redirect重定向会丢失参数,只能慎用。

 

5  利用struts的同步令牌机制   
  利用同步令牌(Token)机制来解决Web应用中重复提交的问题,Struts也给出了一个参考实现。 
  基本原理:  
  服务器端在处理到达的请求之前,会将请求中包含的令牌值与保存在当前用户会话中的令牌值进行比较,看是否匹配。在处理完该请求后,且在答复发送给客户端之前,将会产生一个新的令牌,该令牌除传给客户端以外,也会将用户会话中保存的旧的令牌进行替换。这样如果用户回退到刚才的提交页面并再次提交的话,客户端传过来的令牌就和服务器端的令牌不一致,从而有效地防止了重复提交的发生。  
  if (isTokenValid(request, true)) { 
   // your code here 
   return mapping.findForward("success"); 
  } else { 
   saveToken(request); 
   return mapping.findForward("submitagain"); 
  }  
  Struts根据用户会话ID和当前系统时间来生成一个唯一(对于每个会话)令牌的,具体实现可以参考TokenProcessor类中的generateToken()方法。   
  1. //验证事务控制令牌,<html:form >会自动根据session中标识生成一个隐含input代表令牌,防止两次提交 
  2. 在action中:   
   //<input type="hidden" name="org.apache.struts.taglib.html.TOKEN" 
   // value="6aa35341f25184fd996c4c918255c3ae"> 
  if (!isTokenValid(request)) 
   errors.add(ActionErrors.GLOBAL_ERROR, 
   new ActionError("error.transaction.token")); 
   resetToken(request); //删除session中的令牌   
  3. action有这样的一个方法生成令牌   
   protected String generateToken(HttpServletRequest request) {   
   HttpSession session = request.getSession(); 
   try { 
   byte id[] = session.getId().getBytes(); 
   byte now[] =new Long(System.currentTimeMillis()).toString().getBytes(); 
   MessageDigest md = MessageDigest.getInstance("MD5"); 
   md.update(id); 
   md.update(now); 
   return (toHex(md.digest())); 
   } catch (IllegalStateException e) { 
   return (null); 
   } catch (NoSuchAlgorithmException e) { 
   return (null); 
   } 
   } 

 

5.1 Struts2防止表单重复提交

在开发的时候,很多时候会需要防止用户重复提交表单。在Strut2中可以方便的实现这个功能。

防止表单重复提交主要用到的标签是<s: token />,拦截器 <interceptor-ref name="token" />,还有一个默认的返回值<result name="invalid.token">/input.jsp</result>  

在页面加载时,<s: token />产生一个GUID(Globally Unique Identifier,全局唯一标识符)值的隐藏输入框如:

<input type="hidden" name="struts.token.name" value="struts.token"/>

<input type="hidden" name="struts.token" value="BXPNNDG6BB11ZXHPI4E106CZ5K7VNMHR"/>

同时,将GUID放到会话(session)中;在执行action之前,“token”拦截器将会话token与请求token比较,如果两者相同,则将会话中的token删除并往下执行,否则向actionErrors加入错误信息。如此一来,如果用户通过某种手段提交了两次相同的请求,两个token就会不同。注意:产生的html代码中,struts.token.name是永远不变的,而struts.token是可以改变的(通过在jsp中引入标签<s:token name="你想取的名字"/>,这里的name属性就会取代掉struts.token),另外,两个struts.token是关联在一起的。

举个例子:

以下为action代码

package com.polaris.token;

import com.opensymphony.xwork2.ActionSupport;

public class TokenAction extends ActionSupport

{

private static final long serialVersionUID = 1L;

private String message;

public String getMessage()

{

return message;

}

public void setMessage(String message)

{

this.message = message;

}

@Override

public String execute() throws Exception

{

System.out.println("执行Action, 输出内容:" + getMessage());

return SUCCESS;

}

}

清单1TokenAction代码

再看看JSP的写法

<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8" %>

<%@ taglib prefix="s" uri="/struts-tags" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">

<head>

    <title>struts token</title>

    <s:head />

</head>

<body>    

    <s:actionerror />

    <s:form action="token" >

        <s:textfield name="message" label="Message" />

        <s:token />    <%-- 注意这里,记得放在form表单内,可以自己给token命名--%>

        <s:submit />

    </s:form>

</body>

</html>

清单2message.jsp

下面是struts2的配置文件

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE struts PUBLIC

"-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"

"http://struts.apache.org/dtds/struts-2.0.dtd">

<struts>

<package name="struts2" extends="struts-default">

<action name="token" class="com.polaris.token.TokenAction">

             <result name="invalid.token">/message.jsp</result> <!--注意此处-->

<result>/message.jsp</result>

           <interceptor-ref name="defaultStack" />

           <interceptor-ref name="token" />

        </action>

    </package>

</struts>

清单3struts.xml

以上XML片段值注意的是加入了“token”拦截器和“invalid.token”结果,因为“token”拦截器在会话token与请求token不一致时,将会直接返回“invalid.token”结果。

启动服务器,在浏览器地址栏输入:http://localhost:8080/struts2/message.jsp

错误提示信息是Struts2给我们定义好的(在struts2-core.jar内的org.apache.struts2包下有一个struts-message.properties资源文件。里面定义了一些可配置常量,其中就有struts.messages.invalid.token)

具体配置如下:

  • 在src文件夹中加入struts.properties文件,内容如下:

    struts.custom.i18n.resources=testToken

      Struts 2.0有两个配置文件,struts.xml和struts.properties都是放在WEB-INF/classes/下。

    1. struts.xml用于应用程序相关的配置
    2. struts.properties用于Struts 2.0的运行时(Runtime)的配置

  • 在src文件夹中加入testToken_zh_CN.properties文件,内容如下:

    struts.messages.invalid.token=你已经提交了,不能再提交!

    在此只实现了中文显示(对多语言可以配置相应的内容)

     

    5.2 纯jsp中判断(不建议)

    ﹤input type="hidden " name=" ﹤% =com.lims.util.SynchroToken.TOKEN_NAME%﹥ " value =" ﹤%= com.lims.util.SynchroToken.getToken(request)%﹥" ﹥    

     

    SynchroToken.java  

    package com.lims.util;  

    import org.apache.struts.util.*;  

    import javax.servlet.http.*;  

    import javax.servlet.jsp.*;  

    import org.apache.struts.action.*;  

    /**  

      * ﹤p﹥Title: SynchroToken ﹤/p﹥  

      * ﹤p﹥Description: ﹤/p﹥  

      * ﹤p﹥Copyright: Copyright (c) 2004﹤/p﹥  

      * ﹤p﹥Company: NetStar﹤/p﹥  

      * @author Jstar  

      * @version 1.0  

      * Created in 2004/04/21  

      */  

    public class SynchroToken{  

     public final static java.lang.String TOKEN_NAME = "_token";  

     public static boolean checkToken (HttpServletRequest request){  

       boolean isEqual = false;  

       HttpSession session = request.getSession ();  

       String formToken = request.getParameter (TOKEN_NAME);  

       String sessionToken = (String)session.getAttribute (TOKEN_NAME);  

       System.out.println ("formToken: " + formToken + " sessionToken: " +  

                           sessionToken);  

       if (formToken != null && sessionToken == null){  

         session.setAttribute (TOKEN_NAME, formToken);  

         isEqual = true;  

       }  

       return isEqual;  

     }  

     /**  

      * Insert the method's description here.  

      * Creation date: (4/19/2004 3:23:25 PM)  

      * @return java.lang.String  

      * @param request javax.servlet.http.HttpServletRequest  

      */  

     public static String getToken (HttpServletRequest request){  

       String token = "" + System.currentTimeMillis ();  

       HttpSession session = request.getSession ();  

       if (session != null){  

         session.removeAttribute (TOKEN_NAME);  

       }  

       return token;z  

     }  

     /**  

      * Insert the method's description here.  

      * Creation date: (4/19/2004 3:24:10 PM)  

      * @return java.lang.String  

      */  

     final static java.lang.String getTOKEN_NAME (){  

       return TOKEN_NAME;  

     }  

     public static String message (PageContext pageContext, String key) throws  

         JspException{  

       return RequestUtils.message (pageContext, null, null, key);  

     }  

    }   

     
      
     

     5.1 防止后退的场景

    javascript:window.history.forward

  • 接下来我们要讨论的方法以后退按钮本身为中心,而不是浏览器缓存。这儿有一篇文章Rewiring the Back Button很值得参考。不过我注意到,如果使用这种方法,虽然用户点击一下后退按钮时他不会看到以前输入数据的页面,但只要点击两次就可以,这可不是我们希望的效果,因为很多时候,固执的用户总是能够找到绕过预防措施的办法。


  •   另外一种禁用后退按钮的办法是用客户端JavaScript打开一个没有工具条的窗口,这使得用户很难返回前一页面,但不是不可能。一种更安全但相当恼人的方法是,当表单提交时打开一个新的窗口,与此同时关闭表单所在的窗口。但我觉得这种方法不值得认真考虑,因为我们总不能让用户每提交一个表单就打开一个新窗口。


  •   那么,在那个我们不想让用户返回的页面是否也可以加入JavaScript代码呢?在这个页面中加入的JavaScript代码可用来产生点击前进按钮的效果,这样也就抵消了用户点击后退按钮所产生的动作。用于实现该功能的JavaScript代码如下:
    所示:


  • <script language="JavaScript">
    <!--
    javascript:
    window.history.forward(1);
    //-->
    </script>


  •   同样地,这种方法虽然有效,但距离“最好的方法”还差得很远。后来我又看到有人建议用location.replace从一个页面转到另一个页面。这种方法的原理是,用新页面的URL替换当前的历史纪录,这样浏览历史记录中就只有一个页面,后退按钮永远不会变为可用。我想这可能正是许多人所寻求的方法,但这种方法仍旧不是任何情况下的最好方法。使用这种
    方法的实例如下所示:

  • <A HREF="PageName.htm" onclick="javascript:location.replace(this.href);
    event.returnValue=false; ">
    禁止后退到本页面的链接</A>

  •    禁止后退到本页面的链接!
      这种方法的缺点在于:简单地运用Response.Redirect将不再有效,这是因为每次用户从一个页面转到另一个页面,我们都必须用客户端代码清除location.history。另外还要注意,这种方法清除的是最后一个访问历史记录,而不是全部的访问记录。
      点击上面的链接,你将打开一个简单的HTML页面。再点击后退按钮,你可以看到这时打开的不是本页面,而是本页面之前的页面!(当然,你必须在浏览器中启用了客户端JavaScript代码。)

  •