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

来源:互联网 发布:彩虹timez知乎 编辑:程序博客网 时间:2024/05/16 09:08

原文链接:http://blog.chinaunix.net/uid-26284395-id-3044216.html

利用同步令牌来解决重读提交的基本原理
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
import java.security.MessageDigest;import java.security.NoSuchAlgorithmException;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpSession;/** * TokenProcessor类是一个单例类。*/public class TokenProcessor{    static final String TOKEN_KEY="org.sunxin.token";       private static TokenProcessor instance = new TokenProcessor();    /**     * getInstance()方法得到单例类的实例。     */    public static TokenProcessor getInstance()    {        return instance;    }    /**     * 最近一次生成令牌值的时间戳。     */    private long previous;    /**     * 判断请求参数中的令牌值是否有效。     */    public synchronized boolean isTokenValid(HttpServletRequest request)    {        //得到请求的当前Session对象。        HttpSession session = request.getSession(false);        if (session == null)        {            return false;        }                //从Session中取出保存的令牌值。        String saved = (String) session.getAttribute(TOKEN_KEY);        if (saved == null) {            return false;        }                //清除Session中的令牌值。        resetToken(request);                        //得到请求参数中的令牌值。        String token = request.getParameter(TOKEN_KEY);        if (token == null) {            return false;        }                return saved.equals(token);    }    /**     * 清除Session中的令牌值。     */    public synchronized void resetToken(HttpServletRequest request)    {        HttpSession session = request.getSession(false);        if (session == null) {            return;        }        session.removeAttribute(TOKEN_KEY);    }    /**     * 产生一个新的令牌值,保存到Session中,     * 如果当前Session不存在,则创建一个新的Session。     */    public synchronized void saveToken(HttpServletRequest request)    {        HttpSession session = request.getSession();        String token = generateToken(request);        if (token != null) {            session.setAttribute(TOKEN_KEY, token);        }    }    /**     * 根据用户会话ID和当前的系统时间生成一个唯一的令牌。     */    public synchronized String generateToken(HttpServletRequest request)    {        HttpSession session = request.getSession();        try        {            byte id[] = session.getId().getBytes();            long current = System.currentTimeMillis();            if (current == previous)            {                current++;            }            previous = current;            byte now[] = new Long(current).toString().getBytes();            MessageDigest md = MessageDigest.getInstance("MD5");            md.update(id);            md.update(now);            return toHex(md.digest());        }        catch (NoSuchAlgorithmException e)        {            return null;        }    }    /**     * 将一个字节数组转换为一个十六进制数字的字符串。     */    private String toHex(byte buffer[])    {        StringBuffer sb = new StringBuffer(buffer.length * 2);        for (int i = 0; i < buffer.length; i++)        {            sb.append(Character.forDigit((buffer[i] & 0xf0) >> 4, 16));            sb.append(Character.forDigit(buffer[i] & 0x0f, 16));        }        return sb.toString();    }        /**     * 从Session中得到令牌值,如果Session中没有保存令牌值,则生成一个新的令牌值。     */    public synchronized String getToken(HttpServletRequest request)    {        HttpSession session = request.getSession(false);        if(null==session)            return null;        String token=(String)session.getAttribute(TOKEN_KEY);        if(null==token)        {           token = generateToken(request);            if (token != null)            {                session.setAttribute(TOKEN_KEY, token);                return token;            }            else                return null;        }        else            return token;    }}

2  index.jsp
增加一个隐藏域 并以服务器端产生的令牌值作为他的值
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%><%@ include file="header.jsp" %> //封装了request.getContextPath<%@ page import="org.sunxin.ch19.util.TokenProcessor" %><%String path = request.getContextPath();String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";%><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"><html>  <head>    <base href="<%=basePath%>">    <title>My JSP 'login.jsp' starting page</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">   </head>  <body>   <%    //获取令牌类实例    TokenProcessor processor = TokenProcessor.getInstance();    //获取令牌值    String token = processor.getToken(request);   %>    <form action="${ctx}/servlet/handle" name="theForm" method="post">     <table>      <tr>       <td>用户名:</td>       <td><input type="text" name="username"/></td>      </tr>      <tr>       <td>密码:</td>       <td>       <input type="password" name="password"/>    <%--设置隐藏域,其值为令牌值--%>    <input type="hidden" name="org.sunxin.token" value="<%=token%>"/>       </td>      </tr>      <tr>       <td>        <input type="reset" value="重设">       </td>       <td>        <input type="submit" value="提交" name="btnSubmit" >       </td>      </tr>     </table>    </form>   </body></html>

3  HandlerServlet.java
import java.io.IOException;import java.io.PrintWriter;import javax.servlet.ServletException;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import org.sunxin.ch19.util.TokenProcessor;public class HandlerServlet extends HttpServlet{    int count=0;    public void doPost(HttpServletRequest req, HttpServletResponse resp)                    throws ServletException,IOException    {        resp.setContentType("text/html;charset=GBK");        PrintWriter out=resp.getWriter();                TokenProcessor processor=TokenProcessor.getInstance();        if(processor.isTokenValid(req))        {            try            {                Thread.sleep(5000);            }            catch(InterruptedException e)            {                System.out.println(e);            }                            System.out.println("submit : "+count);            if(count%2==1)                count=0;            else                count++;            out.println("success");        }        else        {            processor.saveToken(req);            out.println("你已经提交了表单,同一表单不能提交两次。");        }        out.close();    }}

原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 天猫超市多发货怎么办 天猫中不小心取消退款了怎么办 天猫超市写错了怎么办 二维码收付款不到红包怎么办 天猫优惠劵过期了怎么办 天猫购物津贴用不完怎么办 天猫上买的大件东西实物不符怎么办 天猫上面料成分与实物不符怎么办 闲鱼发货与实物不符怎么办 天猫超市买贵了怎么办 天猫超市里购买的东西退货怎么办 淘宝店上传的图片不清楚怎么办 微信图片打印出来不清楚怎么办 微信图片打印不清楚怎么办 淘宝上传商品视频不清楚怎么办 手机安装器没了怎么办 我不做直播换工作怎么办 天猫发票被投诉怎么办 天猫机顶盒闪退怎么办 苹果8红色掉漆怎么办 毛坯房验房房及厅试水时漏水怎么办 淘宝退货赠品被拆了怎么办 如果淘宝买家说赠品不好怎么办 淘宝顾客反应没赠品怎么办 淘宝上买东西赠品不给怎么办 天猫店关了质量有问题怎么办 蘑菇街开店被骗了怎么办 百视通网络机顶盒恢复出厂后怎么办 王牌电视出现无信号怎么办 联通电视串台了怎么办 电视上出现系统更新怎么办 电视开机一直在更新怎么办 云视听极光闪退怎么办 不小心打错电话怎么办 相亲发信息不回怎么办 如果一个人微信不回电话不接怎么办 跟老公吵架打电话不接怎么办 起诉离婚对方不接电话怎么办 苹果手机接电话声音小怎么办 老公不回你微信怎么办 工地欠货款不给怎么办