首页
登录 | 注册

在服务器端避免表单的重复提交

利用同步令牌来解决重读提交的基本原理
1 用户访问包含表单的页面  服务器在这次会话中 创建一个session对象  并产生一个令牌值 将这个令牌值作为隐藏输入域值 随表单一起发送到客户端 同时将令牌值保存到session中
2 用户提交页面 服务器端首先判断请求参数中的令牌值和Session中保存的令牌值是否相等 如果相等 则清楚session的令牌值 然后执行数据处理操作 如果不相等 则提示用户已经提交过表单 同时产生一个新的令牌值保存到session中 当用户重读提交数据页面的时候 将新产生的令牌值最为隐藏输入域的值
 
TokenProcessor类主要提供下列方法
 
public java.lang.String generateToken(HttpServletRequest request)
根据当前用户会话ID和当前的系统时间生成一个唯一的令牌值
 
public void savaToken(HttpServletRequest request)
调用generateToken()方法产生一个令牌值 并把它保存到Session中 如果Session不存在 则创建一个新的Session
 
public void resetToken(HttpServletRequest request)
清楚保存在用户Session中的令牌值
 
public boolean isTokenValid(HttpServletRequest request)
public boolean isTokenValid(HttpServletRequest request, boolean reset)
以上两种方法获取请求参数中的令牌值 并与保存在用户Session中的令牌值进行比较 判断是否相等
参数reset表示检测后是否要清楚保存在用户Session中的令牌值 前一个方法调用后一个方法 并给reset传递参数false 即在检测后不清楚Session的令牌值
 
 
 
例子:
1  令牌处理类 Tokenprocessor.java
  1. package org.sunxin.ch19.util;

  2. import java.security.MessageDigest;
  3. import java.security.NoSuchAlgorithmException;

  4. import javax.servlet.http.HttpServletRequest;
  5. import javax.servlet.http.HttpSession;

  6. /**
  7.  * TokenProcessor类是一个单例类。
  8. */
  9. public class TokenProcessor
  10. {
  11.     static final String TOKEN_KEY="org.sunxin.token";
  12.    
  13.     private static TokenProcessor instance = new TokenProcessor();

  14.     /**
  15.      * getInstance()方法得到单例类的实例。
  16.      */
  17.     public static TokenProcessor getInstance()
  18.     {
  19.         return instance;
  20.     }

  21.     /**
  22.      * 最近一次生成令牌值的时间戳。
  23.      */
  24.     private long previous;

  25.     /**
  26.      * 判断请求参数中的令牌值是否有效。
  27.      */
  28.     public synchronized boolean isTokenValid(HttpServletRequest request)
  29.     {
  30.         //得到请求的当前Session对象。

  31.         HttpSession session = request.getSession(false);
  32.         if (session == null)
  33.         {
  34.             return false;
  35.         }
  36.         
  37.         //从Session中取出保存的令牌值。

  38.         String saved = (String) session.getAttribute(TOKEN_KEY);
  39.         if (saved == null) {
  40.             return false;
  41.         }
  42.         
  43.         //清除Session中的令牌值。

  44.         resetToken(request);
  45.         
  46.         
  47.         //得到请求参数中的令牌值。

  48.         String token = request.getParameter(TOKEN_KEY);
  49.         if (token == null) {
  50.             return false;
  51.         }
  52.         
  53.         return saved.equals(token);
  54.     }

  55.     /**
  56.      * 清除Session中的令牌值。
  57.      */
  58.     public synchronized void resetToken(HttpServletRequest request)
  59.     {

  60.         HttpSession session = request.getSession(false);
  61.         if (session == null) {
  62.             return;
  63.         }
  64.         session.removeAttribute(TOKEN_KEY);
  65.     }

  66.     /**
  67.      * 产生一个新的令牌值,保存到Session中,
  68.      * 如果当前Session不存在,则创建一个新的Session。
  69.      */
  70.     public synchronized void saveToken(HttpServletRequest request)
  71.     {

  72.         HttpSession session = request.getSession();
  73.         String token = generateToken(request);
  74.         if (token != null) {
  75.             session.setAttribute(TOKEN_KEY, token);
  76.         }

  77.     }

  78.     /**
  79.      * 根据用户会话ID和当前的系统时间生成一个唯一的令牌。
  80.      */
  81.     public synchronized String generateToken(HttpServletRequest request)
  82.     {

  83.         HttpSession session = request.getSession();
  84.         try
  85.         {
  86.             byte id[] = session.getId().getBytes();
  87.             long current = System.currentTimeMillis();
  88.             if (current == previous)
  89.             {
  90.                 current++;
  91.             }
  92.             previous = current;
  93.             byte now[] = new Long(current).toString().getBytes();
  94.             MessageDigest md = MessageDigest.getInstance("MD5");
  95.             md.update(id);
  96.             md.update(now);
  97.             return toHex(md.digest());
  98.         }
  99.         catch (NoSuchAlgorithmException e)
  100.         {
  101.             return null;
  102.         }
  103.     }

  104.     /**
  105.      * 将一个字节数组转换为一个十六进制数字的字符串。
  106.      */
  107.     private String toHex(byte buffer[])
  108.     {
  109.         StringBuffer sb = new StringBuffer(buffer.length * 2);
  110.         for (int i = 0; i < buffer.length; i++)
  111.         {
  112.             sb.append(Character.forDigit((buffer[i] & 0xf0) >> 4, 16));
  113.             sb.append(Character.forDigit(buffer[i] & 0x0f, 16));
  114.         }
  115.         return sb.toString();
  116.     }
  117.     
  118.     /**
  119.      * 从Session中得到令牌值,如果Session中没有保存令牌值,则生成一个新的令牌值。
  120.      */
  121.     public synchronized String getToken(HttpServletRequest request)
  122.     {
  123.         HttpSession session = request.getSession(false);
  124.         if(null==session)
  125.             return null;
  126.         String token=(String)session.getAttribute(TOKEN_KEY);
  127.         if(null==token)
  128.         {
  129.            token = generateToken(request);
  130.             if (token != null)
  131.             {
  132.                 session.setAttribute(TOKEN_KEY, token);
  133.                 return token;
  134.             }
  135.             else
  136.                 return null;
  137.         }
  138.         else
  139.             return token;
  140.     }
  141. }
 
 
2  index.jsp
增加一个隐藏域 并以服务器端产生的令牌值作为他的值
 
  1. <%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
  2. <%@ include file="header.jsp" %> //封装了request.getContextPath
  3. <%@ page import="org.sunxin.ch19.util.TokenProcessor" %>
  4. <%
  5. String path = request.getContextPath();
  6. String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
  7. %>

  8. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
  9. <html>
  10.   <head>
  11.     <base href="<%=basePath%>">
  12.     <title>My JSP 'login.jsp' starting page</title>
  13.  <meta http-equiv="pragma" content="no-cache">
  14.  <meta http-equiv="cache-control" content="no-cache">
  15.  <meta http-equiv="expires" content="0">
  16.  <meta http-equiv="keywords" content="keyword1,keyword2,keyword3">
  17.  <meta http-equiv="description" content="This is my page">

  18.   </head>
  19.   <body>
  20.    <%
  21.     //获取令牌类实例

  22.     TokenProcessor processor = TokenProcessor.getInstance();
  23.     //获取令牌值

  24.     String token = processor.getToken(request);
  25.    %>
  26.     <form action="${ctx}/servlet/handle" name="theForm" method="post">
  27.      <table>
  28.       <tr>
  29.        <td>用户名:</td>
  30.        <td><input type="text" name="username"/></td>
  31.       </tr>
  32.       <tr>
  33.        <td>密码:</td>
  34.        <td>
  35.        <input type="password" name="password"/>
  36.     <%--设置隐藏域,其值为令牌值--%>
  37.     <input type="hidden" name="org.sunxin.token" value="<%=token%>"/>
  38.        </td>
  39.       </tr>
  40.       <tr>
  41.        <td>
  42.         <input type="reset" value="重设">
  43.        </td>
  44.        <td>
  45.         <input type="submit" value="提交" name="btnSubmit" >
  46.        </td>
  47.       </tr>
  48.      </table>
  49.     </form>
  50.   </body>
  51. </html>
 
3  HandlerServlet.java
  1. package org.sunxin.ch19.servlet;

  2. import java.io.IOException;
  3. import java.io.PrintWriter;

  4. import javax.servlet.ServletException;
  5. import javax.servlet.http.HttpServlet;
  6. import javax.servlet.http.HttpServletRequest;
  7. import javax.servlet.http.HttpServletResponse;

  8. import org.sunxin.ch19.util.TokenProcessor;

  9. public class HandlerServlet extends HttpServlet
  10. {
  11.     int count=0;
  12.     public void doPost(HttpServletRequest req, HttpServletResponse resp)
  13.                     throws ServletException,IOException
  14.     {
  15.         resp.setContentType("text/html;charset=GBK");
  16.         PrintWriter out=resp.getWriter();
  17.         
  18.         TokenProcessor processor=TokenProcessor.getInstance();
  19.         if(processor.isTokenValid(req))
  20.         {
  21.             try
  22.             {
  23.                 Thread.sleep(5000);
  24.             }
  25.             catch(InterruptedException e)
  26.             {
  27.                 System.out.println(e);
  28.             }
  29.                 
  30.             System.out.println("submit : "+count);
  31.             if(count%2==1)
  32.                 count=0;
  33.             else
  34.                 count++;
  35.             out.println("success");
  36.         }
  37.         else
  38.         {
  39.             processor.saveToken(req);
  40.             out.println("你已经提交了表单,同一表单不能提交两次。");
  41.         }
  42.         out.close();
  43.     }
  44. }
 

相关文章

  • 更多Java培训.Java视频教程学习资料,请登录尚硅谷网站下载:www.atguigu.com  1.Javaweb技术的结构 1.2. 结构图说明:整体分为四个部分:1. 黑线: JavaScript相关技术路线2. 蓝线: Servl ...
  • 方法一 System.Net.WebClient WebClientObj        = new System.Net.WebClient();    System.Collections.Specialized.NameValueCo ...
  • Java 创建、填充PDF表单域
    表单域,可以按用途分为多种不同的类型,常见的有文本框.多行文本框.密码框.隐藏域.复选框.单选框和下拉选择框等,目的是用于采集用户的输入或选择的数据. 下面的示例中,将分享通过Java编程在PDF中添加以及填充表单域的方法.包括:文本框.复 ...
  • 推荐一个以动画效果显示github提交记录的黑科技工具:Gource
    先看一些动画效果的截图吧. 推荐一个以动画效果显示github提交记录的黑科技工具:Gource http://www.365yg.com/i6595151386688619022/#mid=1592562064545805 要获取更多Je ...
  • 如果同时用ajax和post提交先执行哪个呢?是ajax返回后再执行post呢还是同时执行? ajax的post提交方式和传统的post提交方式哪个更快? php这个答案描述的挺清楚的:http://www.goodpm.net/postr ...
  • 一.SSI解释 SSI是一种类似于ASP的基于服务器的网页制作技术.将内容发送到浏览器之前,可以使用"服务器端包含 (SSI)"指令将文本.图形或应用程序信息包含到网页中.例如,可以使用 SSI 包含时间/日期戳.版权声 ...

2020 unjeep.com webmaster#unjeep.com
12 q. 0.012 s.
京ICP备10005923号