1). 重复提交的情况:

①. 在表单提交到一个 Servlet, 而 Servlet 又通过请求转发的方式响应一个 JSP(HTML) 页面,
此时地址栏还保留着 Serlvet 的那个路径, 在响应页面点击 "刷新"
②. 在响应页面没有到达时重复点击 "提交按钮".
③. 点击 "返回", 再点击 "提交"

2). 不是重复提交的情况:

点击 "返回", "刷新" 原表单页面, 再 "提交"。

3). 如何避免表单的重复提交:

在表单中做一个标记, 提交到 Servlet 时, 检查标记是否存在且是否和预定义的标记一致, 若一致, 则受理请求,
并销毁标记, 若不一致或没有标记, 则直接响应提示信息: "重复提交"

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

②.利用Session对表单进行token标识

  1. 在原表单页面, 生成一个随机值 token
  2. 在原表单页面, 把 token 值放入 session 属性中
  3. 在原表单页面, 把 token 值放入到 隐藏域 中.
  4. 在目标的 Servlet 中: 获取 session 和 隐藏域 中的 token 值
  5. 比较两个值是否一致: 若一致, 受理请求, 且把 session 域中的 token 属性清除
  6. 若不一致, 则直接响应提示页面: "重复提交"

表单页面

<%@ page import="java.util.Date" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<%
//随机生成一个标识
String tokenValue = new Date().getTime()+"";
session.setAttribute("token",tokenValue);
%>
<form action="<%=request.getContextPath()%>/tokenServlet" method="post">
<input type="hidden" name="tokenValue" value=<%=tokenValue%> />
Username:<input type="text" name="username"/>
<input type="submit" value="登录"/>
</form>
</body>
</html>

TokenServlet.java

package yang.mybatis.servlet.token;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException; /**
* Created by yangshijing on 2017/11/22 0022.
*/
public class TokenServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request,response);
} protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//模仿后台延时
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
HttpSession session = request.getSession();
//从Session域中获得标识
String token = (String)session.getAttribute("token");
//从表单隐藏域中获取token value
String tokenValue = request.getParameter("tokenValue");
//如果标识不为空且隐藏域中值和Session域中的值一致,则处理该表单请求
if(token != null && tokenValue.equals(token)){
session.removeAttribute("token");
}else{
response.sendRedirect(request.getContextPath()+"/jsp/token/token.jsp");
return;
}
request.getRequestDispatcher("/jsp/token/success.jsp").forward(request,response);
}
}

TokenProcessor.java

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

package yang.mybatis.servlet.token;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
/**
* Created by yangshijing on 2017/11/23 0023.
*/
public class TokenProcessor {
//表单隐藏域的name属性值
private static final String TOKEN_KEY = "TOKEN_KEY";
//Session域中的属性值
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) {
HttpSession session = request.getSession(false); if (session == null) {
return false;
}
//从Session域中获取标识值
String saved = (String) session.getAttribute(TRANSACTION_TOKEN_KEY); if (saved == null) {
return false;
} String token = request.getParameter(TOKEN_KEY); if (token == null) {
return false;
} return saved.equals(token);
}
//移除Session域中的token
public synchronized void resetToken(HttpServletRequest request) {
HttpSession session = request.getSession(false); if (session == null) {
return;
} session.removeAttribute(TRANSACTION_TOKEN_KEY);
}
//将token保存到Session域中并返回token
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();
}
}

重构的index.jsp

<%@ page import="java.util.Date" %>
<%@ page import="yang.mybatis.servlet.token.TokenProcessor" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<%--<%
String tokenValue = new Date().getTime()+"";
session.setAttribute("token",tokenValue);
%>--%>
<form action="<%=request.getContextPath()%>/tokenServlet" method="post">
<input type="hidden" name="TOKEN_KEY" value=<%=TokenProcessor.getInstance().saveToken(request)%> />
Username:<input type="text" name="username"/>
<input type="submit" value="登录"/>
</form>
</body>
</html>

重构的TokenServlet

package yang.mybatis.servlet.token;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException; /**
* Created by yangshijing on 2017/11/22 0022.
*/
public class TokenServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request,response);
} protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//模仿后台延时
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
/* HttpSession session = request.getSession();
//从Session域中获得标识
String token = (String)session.getAttribute("token");
//从表单隐藏域中获取token value
String tokenValue = request.getParameter("tokenValue");
//如果标识不为空且隐藏域中值和Session域中的值一致,则处理该表单请求
if(token != null && tokenValue.equals(token)){
session.removeAttribute("token");
}else{
response.sendRedirect(request.getContextPath()+"/jsp/token/token.jsp");
return;
}
request.getRequestDispatcher("/jsp/token/success.jsp").forward(request,response);*/
boolean tokenValid = TokenProcessor.getInstance().isTokenValid(request);
//如果不是重复的表单请求,移除token并处理请求
if(tokenValid){
TokenProcessor.getInstance().resetToken(request);
}else {
//重复的表单请求,结束方法,并重定向到提示页面
response.sendRedirect(request.getContextPath()+"/jsp/token/token.jsp");
return;
}
response.sendRedirect(request.getContextPath()+"/jsp/token/success.jsp");
}
}

HttpSession解决表单的重复提交的更多相关文章

  1. HttpSession之表单的重复提交 & 验证码

    如果采用 HttpServletResponse.sendRedirct() 方法将客户端重定向到成功页面,将不会出现重复提交问题 1.表单的重复提交 1). 重复提交的情况: ①. 在表单提交到一个 ...

  2. struts2 文件的上传下载 表单的重复提交 自定义拦截器

    文件上传中表单的准备 要想使用 HTML 表单上传一个或多个文件 须把 HTML 表单的 enctype 属性设置为 multipart/form-data 须把 HTML 表单的method 属性设 ...

  3. php中如何防止表单的重复提交

    在php中如何防止表单的重复提交?其实也有几种解决方法. 下面小编就为大家介绍一下吧.需要的朋友可以过来参考下 代码: <?php /* * php中如何防止表单的重复提交 * by www.j ...

  4. Spring MVC表单防重复提交

    利用Spring MVC的过滤器及token传递验证来实现表单防重复提交. 创建注解 @Target(ElementType.METHOD) @Retention(RetentionPolicy.RU ...

  5. Session机制三(表单的重复提交)

    1.表单的重复提交的情况 在表单提交到一个servlet,而servlet又通过请求转发的方式响应了一个JSP页面,这个时候地址栏还保留这servlet的那个路径,在响应页面点击刷新. 在响应页面没有 ...

  6. Struts2 - 表单的重复提交问题

    用户重复提交表单在某些场合将会造成非常严重的后果.例如,在使用信用卡进行在线支付的时候,如果服务器的响应速度太慢,用户有可能会多次点击提交按钮,而这可能导致那张信用卡上的金额被消费了多次.因此,重复提 ...

  7. 使用aop注解实现表单防重复提交功能

    原文:https://www.cnblogs.com/manliu/articles/5983888.html 1.这里采用的方法是:使用get请求进入表单页面时,后台会生成一个tokrn_flag分 ...

  8. php-- 避免表单的重复提交

    用户提交表单时可能因为网速的原因,或者网页被恶意刷新,致使同一条记录重复插入到数据库中,这是一个比较棘手的问题.我们可以从客户端和服务器端一起着手,设法避免同一表单的重复提交. 1.使用客户端脚本 提 ...

  9. struts2 防止表单的重复提交

    防止表单重复提交(拦截器) 1.回顾之前的解决办法: 2.Struts2中的解决办法: 2.1.使用重定向 <result type="redirect">/succe ...

随机推荐

  1. 如何禁用 FastAdmin 双击编辑功能?

    如何禁用 FastAdmin 双击编辑功能? 新版 (1.0.0.20180513_beta)增加一个新功能,可以禁止双击编辑. 很多人还是喜欢双击选中复制,默认的双击编辑还是不怎么习惯. 可以以下文 ...

  2. Android 中jar包封装及调用-转

    在android开发过程中,我们经常会有这种需求,自己开发一个类库jar包,提供给别人调用. 即把项目A封装成jar包,供项目B调用,而在项目B中调用项目A的activity的时候问题就出现了:找不到 ...

  3. 10 Things ASP.NET Developers Should Know About Web.config Inheritance and Overrides(转)

    10 Things ASP.NET Developers Should Know About Web.config Inheritance and Overrides Wednesday, Janua ...

  4. List,ArrayList

    List是一个接口,而ListArray是一个类. ListArray继承并实现了List. 所以List不能被构造,但可以向上面那样为List创建一个引用,而ListArray就可以被构造. Lis ...

  5. android中asynctask的使用实例

    参考此blog写的非常的好http://www.cnblogs.com/devinzhang/archive/2012/02/13/2350070.html MainActivity.java imp ...

  6. 机器人操作系统(ROS)教程22:ROS的3D可视化工具—rviz

    rviz是ROS中的一个3D可视化工具,有了它就可以把你用代码建的机器人模型转化为可视的3D模型. 首先需要安装: rosdep install rviz 然后编译rviz: rosmake rviz ...

  7. 蓝桥杯 算法训练 ALGO-108 最大的体积

    算法训练 最大体积   时间限制:1.0s   内存限制:256.0MB 问题描述 每个物品有一定的体积(废话),不同的物品组合,装入背包会战用一定的总体积.假如每个物品有无限件可用,那么有些体积是永 ...

  8. Eclipse中的maven项目打war包

    在对应的pom文件中,找到packing这个属性,改为war:如果没有,就自己加一个,这个是有默认值的,默认为jar. 例如: <modelVersion>4.0.0</modelV ...

  9. Intellij idea run dashboard面板

    IDEA下 Spring Boot显示 Run Dashboard面板 Run Dashboard面板允许您一次浏览和管理多个运行项目,左侧是运行配置表及其状态,右侧是详细信息和特定于应用程序的信息, ...

  10. python开发面向对象基础:组合&继承

    一,组合 组合指的是,在一个类中以另外一个类的对象作为数据属性,称为类的组合      人类装备了武器类就是组合 1.圆环,将圆类实例后传给圆环类 #!/usr/bin/env python #_*_ ...