说明
调用 RequestDispatcher.forward() 方法,浏览器所保留的URL 是先前的表单提交的 URL,此时点击”刷新”, 浏览器将再次提交用户先前输入的数据,引起重复提交。如果采用 HttpServletResponse.sendRedirct() 方法将客户端重定向到成功页面,将不会出现重复一条问题。
JS 客户端避免表单重复提交
不足:用户单击”刷新”,或单击”后退”再次提交表单,将导致表单重复提交。
利用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集合对象中删除。
测试代码
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_course.code
欢迎加入交流群:451826376
更多信息:www.itcourse.top