JavaWeb-026-避免表单的重复提交

说明

调用 RequestDispatcher.forward() 方法,浏览器所保留的URL 是先前的表单提交的 URL,此时点击”刷新”, 浏览器将再次提交用户先前输入的数据,引起重复提交。如果采用 HttpServletResponse.sendRedirct() 方法将客户端重定向到成功页面,将不会出现重复一条问题。

JS 客户端避免表单重复提交

JavaWeb-026-避免表单的重复提交_第1张图片
微信公众号:JavaWeb架构师

不足:用户单击”刷新”,或单击”后退”再次提交表单,将导致表单重复提交。

利用SESSION防止表单重复提交

  • 包含有FORM表单的页面必须通过一个服务器程序动态产生,服务器程序为每次产生的页面中的FORM表单都分配一个唯一的随机标识号,并在FORM表单的一个隐藏字段中设置这个标识号,同时在当前用户的Session域中保存这个标识号。

  • 当用户提交FORM表单时,负责接收这一请求的服务器程序比较FORM表单隐藏字段中的标识号与存储在当前用户的Session域中的标识号是否相同,如果相同则处理表单数据,处理完后清除当前用户的Session域中存储的标识号。在下列情况下,服务器程序将忽略提交的表单请求:
    1.当前用户的Session中不存在表单标识号
    2.用户提交的表单数据中没有标识号字段
    3.存储在当前用户的Session域中的表单标识号与表单数据中的标识号不同

  • 浏览器只有重新向WEB服务器请求包含FORM表单的页面时,服务器程序才又产生另外一个随机标识号,并将这个标识号保存在Session域中和作为新返回的FORM表单中的隐藏字段值。

  • TokenProcessor.java:用于管理表单标识号的工具类,它主要用于产生、比较和清除存储在当前用户Session中的表单标识号。为了保证表单标识号的唯一性,每次将当前SessionID和系统时间的组合值按MD5算法计算的结果作为表单标识号,并且将TokenProcessor类设计为单件类

  • 问题:
    同一个用户打开同一个浏览器进程的多个窗口来并发访问同一个WEB站点的多个FORM表单页面时,将会出现表单无法正常提交的情况。

  • 解决方案:
    a) 将FORM表单的标识号作为表单隐藏字段的名称,如下所示:


b) 将所有的表单标识号存储进一个Vector集合对象中,并将Vector集合对象存储进Session域中。当表单提交时,先从Session域中取出Vector集合对象,然后再从Vector集合对象中逐一取出每个表单标识号作为参数调用HttpServletRequest.getParameter方法,如果其中有一次调用的返回值不为null,则接受并处理该表单数据,处理完后将该表单标识号从Vector集合对象中删除。

测试代码

JavaWeb-026-避免表单的重复提交_第2张图片
微信公众号:JavaWeb架构师

index.jsp

<%@page import="com.mac.servlet.TokenProcessor"%>
<%@page import="java.util.Date"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>


    
        
        Insert title here
    
    
    
            <%
                //获取Date值
                //String tokenValue = new Date().getTime() + "";
            %>
            
            <%
                //1.在提交页面设置session属性(这个值,通过隐藏域提交给Servlet)
                //2.在Servlet页面检查是否存在属性,并销毁(隐藏域的值 和 session的属性值)
                //这样可以解决,提交成功之后,返回再提交(直接返回不刷新,不会重新的加载该页面的 内容,只是提交的值是保存着的)
            
                //把Date值放到session中
                //session.setAttribute("token", tokenValue);
            %>
            
        
<%-- --%>
用户名

error.jsp

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


    
        
        Insert title here
    
    
    
        重复提交!!!
    

succeed.jsp

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


    
        
        Insert title here
    
    
    
        <%--

         --%>
        <%
            String username = (String)session.getAttribute("username");
        %>
        
登录成功! <%=username %>

TokenServlet.java

package com.mac.servlet;

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;


public class TokenServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doPost(request, response);
    }


    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //1.获取HTML页面的输入参数
        String username = request.getParameter("username");
        
        //2.转成utf-8
        username = (username == null ? "" : new String(username.getBytes("iso-8859-1"),"utf-8"));
        
        //3.存储属性
        request.getSession().setAttribute("username", username);
        
        /*
         *  通过转发的形式:
         *      - 出现的情况:
         *          1.网络问题或者服务器问题(用户连续点击了多次提交):
         *          2.在提交后的页面进行刷新(因为转发之后url没有变):使用重定向可以避免
         *          3.返回上一级页面之后,再提交(返回之后再刷新,再提交,就不算):使用session属性可以避免
         *      - 坏处:
         *      加重服务器负担
         *      出现数据重复
         */
        //4.进行转发(会出现重复提交的问题)
        //request.getRequestDispatcher("/succeed.jsp").forward(request, response);
        
        /*
         *  通过重定向的形式(解决连续刷新):
         *      - 出现的情况:
         *          1.网络问题或者服务器问题(用户连续点击了多次提交)
         *          2.返回上一级页面之后,再提交(返回之后再刷新,再提交,就不算)
         *      - 坏处:
         *          加重服务器负担
         *          出现数据重复
         */
        
        /*
         *  避免表单重复提交(解决返回之后再提交):
         *      1.在表单中做一个标记(session)【表单页面设置session属性】【注:使用request不行,因为每一次请求都是新的request,所以永远无法提交】。
         *      2.提交到Servlet的时候,检查标记和预定的是否一样,一致就接受请求,并销毁标记(已经被销毁了,会满足第3条)【Servlet中检验Servlet并销毁】。
         *      3.否则就不受理提交内容(还是要使用重定向)。
         *      注:在第一个书写信息的页面,设置的属性和Servlet(这里用隐藏域传过去)这里对比的值,使用Date
         *      -坏处:
         *          1.网络问题或者服务器问题(用户连续点击了多次提交)
         */
//      String tokenHidden = (String)request.getParameter("token");                     //获取隐藏域的值
//      String tokenSession = (String)request.getSession().getAttribute("token");       //获取session设置的值
//      System.out.println(tokenSession);
//      System.out.println(tokenHidden);
//      //进行对比
//      if( tokenSession != null && tokenSession.equals(tokenHidden) ) {
//          System.out.println(username);
//          request.getSession().removeAttribute("token");
//          //进行重定向,提交成功
//          response.sendRedirect(request.getContextPath() + "/succeed.jsp");
//      } else {
//          //进行重定向,重复提交页面
//          response.sendRedirect(request.getContextPath() + "/error.jsp");
//      }
        
        //使用TokenProcessor
        /*
         * ~3.看这个请求对应的TokenProcessor是否可用
         */
        boolean  valid = TokenProcessor.getInstance().isTokenValid(request);
        
        /*
         * ~ 4.1.可用就移除session中的值,并到成功页面
         * ~ 4.2.不可用就转到提示页面
         */
        System.out.println(valid);
        if( valid == true ) {
            TokenProcessor.getInstance().resetToken(request);
            response.sendRedirect(request.getContextPath() + "/succeed.jsp");
        } else {
            response.sendRedirect(request.getContextPath() + "/error.jsp");
            return ;
        }
    }
}

TokenProcessor.java

package com.mac.servlet;

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

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

public class TokenProcessor {

    private static final String TOKEN_KEY = "TOP.ITCOURSE.JAVAWEB.TOKEN_KEY";

    private static final String TRANSACTION_TOKEN_KEY = "TRANSACTION_TOKEN_KEY";

    private static TokenProcessor instance = new TokenProcessor();

    private long previous;

    protected TokenProcessor() {
        super();
    }

    public static TokenProcessor getInstance() {
        return instance;
    }

    public synchronized boolean isTokenValid(HttpServletRequest request) {
        return this.isTokenValid(request, false);
    }

    public synchronized boolean isTokenValid(HttpServletRequest request,
            boolean reset) {
        HttpSession session = request.getSession(false);

        if (session == null) {
            return false;
        }

        String saved = (String) session.getAttribute(TRANSACTION_TOKEN_KEY);

        if (saved == null) {
            return false;
        }

        if (reset) {
            this.resetToken(request);
        }

        String token = request.getParameter(TOKEN_KEY);

        if (token == null) {
            return false;
        }

        return saved.equals(token);
    }

    public synchronized void resetToken(HttpServletRequest request) {
        HttpSession session = request.getSession(false);

        if (session == null) {
            return;
        }

        session.removeAttribute(TRANSACTION_TOKEN_KEY);
    }

    public synchronized String saveToken(HttpServletRequest request) {
        HttpSession session = request.getSession();
        String token = generateToken(request);

        if (token != null) {
            session.setAttribute(TRANSACTION_TOKEN_KEY, token);
        }
        
        return token;
    }

    public synchronized String generateToken(HttpServletRequest request) {
        HttpSession session = request.getSession();

        return generateToken(session.getId());
    }

    public synchronized String generateToken(String id) {
        try {
            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.getBytes());
            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();
    }
}
微信公众号:JavaWeb架构师

其它

  • 源码下载
关注下方公众号,回复:javaweb_course.code
  • 欢迎加入交流群:451826376

  • 更多信息:www.itcourse.top

完整教程PDF版本下载

你可能感兴趣的:(JavaWeb-026-避免表单的重复提交)