如果采用 HttpServletResponse.sendRedirct() 方法将客户端重定向到成功页面,将不会出现重复提交问题

1.表单的重复提交

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

2). 不是重复提交的情况: 点击 "返回", "刷新" 原表单页面, 再 "提交"。

3). 如何避免表单的重复提交: 在表单中做一个标记, 提交到 Servlet 时, 检查标记是否存在且是否和预定义的标记一致,

       若一致, 则受理请求,并销毁标记, 若不一致或没有标记, 则直接响应提示信息: "重复提交"

①. 仅提供一个隐藏域: <input type="hidden" name="token" value="atguigu"/>. 行不通: 没有方法清除固定的请求参数.
②. 把标记放在 request 中. 行不通, 因为表单页面刷新后, request 已经被销毁, 再提交表单是一个新的 request.
③. 把标记放在 session 中. 可以!

jsp中

> 在原表单页面, 生成一个随机值 token
> 在原表单页面, 把 token 值放入 session 属性中
> 在原表单页面, 把 token 值放入到 隐藏域 中.

servlet中

> 在目标的 Servlet 中: 获取 session 和 隐藏域 中的 token 值
> 比较两个值是否一致: 若一致, 受理请求, 且把 session 域中的 token 属性清除
> 若不一致, 则直接响应提示页面: "重复提交"

4).利用Session防止表单重复提交 

TokenProcessor.java:用于管理表单标识号的工具类,它主要用于产生、比较和清除存储在当前用户Session中的表单标识号。

为了保证表单标识号的唯一性,每次将当前SessionID和系统时间的组合值按MD5算法计算的结果作为表单标识号,

并且将TokenProcessor类设计为单件类

实验

TokenProcessor.java

  1. package com.aff.javaweb;
  2.  
  3. import java.security.MessageDigest;
  4. import java.security.NoSuchAlgorithmException;
  5.  
  6. import javax.servlet.http.HttpServletRequest;
  7. import javax.servlet.http.HttpSession;
  8.  
  9. public class TokenProcessor {
  10.  
  11. private static final String TOKEN_KEY = "TOKEN_KEY";
  12. private static final String TRANSACTION_TOKEN_KEY = "TRANSACTION_TOKEN_KEY";
  13. private static TokenProcessor instance = new TokenProcessor();
  14. private long previous;
  15.  
  16. protected TokenProcessor() {
  17. super();
  18. }
  19.  
  20. public static TokenProcessor getInstance() {
  21. return instance;
  22. }
  23.  
  24. public synchronized boolean isTokenValid(HttpServletRequest request) {
  25. return this.isTokenValid(request, false);
  26. }
  27.  
  28. public synchronized boolean isTokenValid(HttpServletRequest request, boolean reset) {
  29. HttpSession session = request.getSession(false);
  30.  
  31. if (session == null) {
  32. return false;
  33. }
  34.  
  35. String saved = (String) session.getAttribute(TRANSACTION_TOKEN_KEY);
  36.  
  37. if (saved == null) {
  38. return false;
  39. }
  40.  
  41. if (reset) {
  42. this.resetToken(request);
  43. }
  44.  
  45. String token = request.getParameter(TOKEN_KEY);
  46.  
  47. if (token == null) {
  48. return false;
  49. }
  50.  
  51. return saved.equals(token);
  52. }
  53.  
  54. public synchronized void resetToken(HttpServletRequest request) {
  55. HttpSession session = request.getSession(false);
  56.  
  57. if (session == null) {
  58. return;
  59. }
  60.  
  61. session.removeAttribute(TRANSACTION_TOKEN_KEY);
  62. }
  63.  
  64. public synchronized String saveToken(HttpServletRequest request) {
  65. HttpSession session = request.getSession();
  66. String token = generateToken(request);
  67.  
  68. if (token != null) {
  69. session.setAttribute(TRANSACTION_TOKEN_KEY, token);
  70. }
  71.  
  72. return token;
  73. }
  74.  
  75. public synchronized String generateToken(HttpServletRequest request) {
  76. HttpSession session = request.getSession();
  77.  
  78. return generateToken(session.getId());
  79. }
  80.  
  81. public synchronized String generateToken(String id) {
  82. try {
  83. long current = System.currentTimeMillis();
  84.  
  85. if (current == previous) {
  86. current++;
  87. }
  88.  
  89. previous = current;
  90.  
  91. byte[] now = new Long(current).toString().getBytes();
  92. MessageDigest md = MessageDigest.getInstance("MD5");
  93.  
  94. md.update(id.getBytes());
  95. md.update(now);
  96.  
  97. return toHex(md.digest());
  98. } catch (NoSuchAlgorithmException e) {
  99. return null;
  100. }
  101. }
  102.  
  103. private String toHex(byte[] buffer) {
  104. StringBuffer sb = new StringBuffer(buffer.length * 2);
  105.  
  106. for (int i = 0; i < buffer.length; i++) {
  107. sb.append(Character.forDigit((buffer[i] & 0xf0) >> 4, 16));
  108. sb.append(Character.forDigit(buffer[i] & 0x0f, 16));
  109. }
  110.  
  111. return sb.toString();
  112. }
  113. }

TokenServlet.java

  1. package com.aff.javaweb;
  2.  
  3. import java.io.IOException;
  4. import javax.servlet.ServletException;
  5. import javax.servlet.annotation.WebServlet;
  6. import javax.servlet.http.HttpServlet;
  7. import javax.servlet.http.HttpServletRequest;
  8. import javax.servlet.http.HttpServletResponse;
  9.  
  10. @WebServlet("/tokenServlet")
  11. public class TokenServlet extends HttpServlet {
  12. private static final long serialVersionUID = 1L;
  13.  
  14. protected void doPost(HttpServletRequest request, HttpServletResponse response)
  15. throws ServletException, IOException {
  16. try {
  17. Thread.sleep(2000);
  18. } catch (InterruptedException e) {
  19. e.printStackTrace();
  20. }
  21. boolean valid = TokenProcessor.getInstance().isTokenValid(request);
  22. if(valid){
  23. TokenProcessor.getInstance().resetToken(request);//清除标识号
  24. }else{
  25. response.sendRedirect(request.getContextPath() + "/token/token.jsp");
  26. return;
  27. }
  28. String name = request.getParameter("name");
  29. System.out.println("name:" + name);
  30. //request.getRequestDispatcher("/token/success.jsp").forward(request, response);
  31. response.sendRedirect(request.getContextPath()+"/token/success.jsp");
  32.  
  33. }
  34.  
  35. }

index.jsp

  1. <%@page import="com.aff.javaweb.TokenProcessor"%>
  2. <%@ page language="java" contentType="text/html; charset=UTF-8"
  3. pageEncoding="UTF-8"%>
  4. <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
  5. <html>
  6. <head>
  7. <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  8. <title>Insert title here</title>
  9. </head>
  10. <body>
  11. <form action="<%=request.getContextPath() %>/tokenServlet" method="post">
  12. <input type="hidden" name="TOKEN_KEY"
  13. value="<%= TokenProcessor.getInstance().saveToken(request) %>"/>
  14.  
  15. name: <input type="text" name="name"/>
  16. <input type="submit" value="Submit"/>
  17. </form>
  18.  
  19. </body>
  20. </html>

success.jsp

  1. <%@ page language="java" contentType="text/html; charset=UTF-8"
  2. pageEncoding="UTF-8"%>
  3. <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
  4. <html>
  5. <head>
  6. <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  7. <title>Insert title here</title>
  8. </head>
  9. <body>
  10. <h3>Success Page</h3>
  11. </body>
  12. </html>

token.jsp

  1. <%@ page language="java" contentType="text/html; charset=UTF-8"
  2. pageEncoding="UTF-8"%>
  3. <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
  4. <html>
  5. <head>
  6. <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  7. <title>Insert title here</title>
  8. </head>
  9. <body>
  10. <h4>对不起, 已经提交过了!</h4>
  11. </body>
  12. </html>

2.使用 HttpSession 实现验证码

1). 基本原理: 和表单重复提交一致:

> 在原表单页面, 生成一个验证码的图片, 生成图片的同时, 需要把该图片中的字符串放入到 session 中.
> 在原表单页面, 定义一个文本域, 用于输入验证码.

> 在目标的 Servlet 中: 获取 session 和 表单域 中的 验证码的 值
> 比较两个值是否一致: 若一致, 受理请求, 且把 session 域中的 验证码 属性清除
> 若不一致, 则直接通过重定向的方式返回原表单页面, 并提示用户 "验证码错误"

ValidateColorServlet.java

  1. package com.aff.javaweb;
  2.  
  3. import java.awt.Color;
  4. import java.awt.Font;
  5. import java.awt.Graphics2D;
  6. import java.awt.image.BufferedImage;
  7. import java.io.IOException;
  8. import java.util.Random;
  9.  
  10. import javax.imageio.ImageIO;
  11. import javax.servlet.ServletException;
  12. import javax.servlet.ServletOutputStream;
  13. import javax.servlet.http.HttpServlet;
  14. import javax.servlet.http.HttpServletRequest;
  15. import javax.servlet.http.HttpServletResponse;
  16.  
  17. public class ValidateColorServlet extends HttpServlet {
  18.  
  19. public static final String CHECK_CODE_KEY = "CHECK_CODE_KEY";
  20.  
  21. private static final long serialVersionUID = 1L;
  22.  
  23. // 设置验证图片的宽度, 高度, 验证码的个数
  24. private int width = 152;
  25. private int height = 40;
  26. private int codeCount = 6;
  27.  
  28. // 验证码字体的高度
  29. private int fontHeight = 4;
  30.  
  31. // 验证码中的单个字符基线. 即:验证码中的单个字符位于验证码图形左上角的 (codeX, codeY) 位置处
  32. private int codeX = 0;
  33. private int codeY = 0;
  34.  
  35. // 验证码由哪些字符组成
  36. char[] codeSequence = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz23456789".toCharArray();
  37.  
  38. // 初始化验证码图形属性
  39. public void init() {
  40. fontHeight = height - 2;
  41. codeX = width / (codeCount + 2);
  42. codeY = height - 4;
  43. }
  44.  
  45. public void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
  46. // 定义一个类型为 BufferedImage.TYPE_INT_BGR 类型的图像缓存
  47. BufferedImage buffImg = null;
  48. buffImg = new BufferedImage(width, height, BufferedImage.TYPE_3BYTE_BGR);
  49.  
  50. // 在 buffImg 中创建一个 Graphics2D 图像
  51. Graphics2D graphics = null;
  52. graphics = buffImg.createGraphics();
  53.  
  54. // 设置一个颜色, 使 Graphics2D 对象的后续图形使用这个颜色
  55. graphics.setColor(Color.WHITE);
  56.  
  57. // 填充一个指定的矩形: x - 要填充矩形的 x 坐标; y - 要填充矩形的 y 坐标; width - 要填充矩形的宽度; height
  58. // - 要填充矩形的高度
  59. graphics.fillRect(0, 0, width, height);
  60.  
  61. // 创建一个 Font 对象: name - 字体名称; style - Font 的样式常量; size - Font 的点大小
  62. Font font = null;
  63. font = new Font("", Font.BOLD, fontHeight);
  64. // 使 Graphics2D 对象的后续图形使用此字体
  65. graphics.setFont(font);
  66.  
  67. graphics.setColor(Color.BLACK);
  68.  
  69. // 绘制指定矩形的边框, 绘制出的矩形将比构件宽一个也高一个像素
  70. graphics.drawRect(0, 0, width - 1, height - 1);
  71.  
  72. // 随机产生 40 条干扰线, 使图像中的认证码不易被其它程序探测到
  73. Random random = null;
  74. random = new Random();
  75. graphics.setColor(Color.GREEN);
  76. for (int i = 0; i < 40; i++) {
  77. int x = random.nextInt(width);
  78. int y = random.nextInt(height);
  79. int x1 = random.nextInt(20);
  80. int y1 = random.nextInt(20);
  81. graphics.drawLine(x, y, x + x1, y + y1);
  82. }
  83.  
  84. // 创建 randomCode 对象, 用于保存随机产生的验证码, 以便用户登录后进行验证
  85. StringBuffer randomCode;
  86. randomCode = new StringBuffer();
  87.  
  88. for (int i = 0; i < codeCount; i++) {
  89. // 得到随机产生的验证码数字
  90. String strRand = null;
  91. strRand = String.valueOf(codeSequence[random.nextInt(36)]);
  92.  
  93. // 把正在产生的随机字符放入到 StringBuffer 中
  94. randomCode.append(strRand);
  95.  
  96. // 用随机产生的颜色将验证码绘制到图像中
  97. graphics.setColor(Color.BLUE);
  98. graphics.drawString(strRand, (i + 1) * codeX, codeY);
  99. }
  100.  
  101. // 再把存放有所有随机字符的 StringBuffer 对应的字符串放入到 HttpSession 中
  102. request.getSession().setAttribute(CHECK_CODE_KEY, randomCode.toString());
  103.  
  104. // 禁止图像缓存
  105. response.setHeader("Pragma", "no-cache");
  106. response.setHeader("Cache-Control", "no-cache");
  107. response.setDateHeader("Expires", 0);
  108.  
  109. // 将图像输出到输出流中
  110. ServletOutputStream sos = null;
  111. sos = response.getOutputStream();
  112. ImageIO.write(buffImg, "jpeg", sos);
  113. sos.close();
  114. }
  115. }

CheckCodeServlet.java

  1. package com.aff.javaweb;
  2.  
  3. import java.io.IOException;
  4.  
  5. import javax.servlet.ServletException;
  6. import javax.servlet.annotation.WebServlet;
  7. import javax.servlet.http.HttpServlet;
  8. import javax.servlet.http.HttpServletRequest;
  9. import javax.servlet.http.HttpServletResponse;
  10.  
  11. @WebServlet("/checkCodeServlet")
  12. public class CheckCodeServlet extends HttpServlet {
  13. private static final long serialVersionUID = 1L;
  14.  
  15. protected void doPost(HttpServletRequest request, HttpServletResponse response)
  16. throws ServletException, IOException {
  17. // 获取请求参数: CHECK_CODE_PARAM_NAME
  18. String paramCode = request.getParameter("CHECK_CODE_PARAM_NAME");
  19. // 获取session 中的CHECK_CODE_KEY 属性值
  20. String sessionCode = (String) request.getSession().getAttribute("CHECK_CODE_KEY");
  21.  
  22. System.out.println(paramCode);
  23. System.out.println(sessionCode);
  24. if (!(paramCode != null && paramCode.equals(sessionCode))) {
  25. // 比对 看是否一致,若一致 说明验证码正确, 若不一致说明验证码错误
  26. request.getSession().setAttribute("message", "验证码不一致");
  27. response.sendRedirect(request.getContextPath() + "/check/index.jsp");
  28. return;
  29.  
  30. }
  31. System.out.println("受理请求!");
  32. }
  33.  
  34. }

index.jsp

  1. <%@ page language="java" contentType="text/html; charset=UTF-8"
  2. pageEncoding="UTF-8"%>
  3. <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
  4. <html>
  5. <head>
  6. <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  7. <title>Insert title here</title>
  8. </head>
  9. <body>
  10. <font color="red">
  11. <%= session.getAttribute("message") == null ? "" : session.getAttribute("message")%>
  12. </font>
  13.  
  14. <form action="<%=request.getContextPath()%>/checkCodeServlet"
  15. method="post">
  16. name: <input type="text" name="name" />
  17. checkCode:<input type="text" name="CHECK_CODE_PARAM_NAME" />
  18. <img alt="" src="<%=request.getContextPath()%>/validateColorServlet">
  19. <input type="submit" value="Submit" />
  20. </form>
  21.  
  22. </body>
  23. </html>

HttpSession之表单的重复提交 & 验证码的更多相关文章

  1. HttpSession解决表单的重复提交

    1). 重复提交的情况: ①. 在表单提交到一个 Servlet, 而 Servlet 又通过请求转发的方式响应一个 JSP(HTML) 页面, 此时地址栏还保留着 Serlvet 的那个路径, 在响 ...

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

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

  3. Spring MVC表单防重复提交

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

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

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

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

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

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

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

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

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

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

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

  9. Token注解防止表单的重复提交

    注解的一些基础: 参见http://blog.csdn.net/duo2005duo/article/details/50505884和 http://blog.csdn.net/duo2005duo ...

随机推荐

  1. VSCode 安装 React 项目

    1 下载nodejs 安装 (此时npm 和 node环境都已经装好) 2 安装淘宝镜像 npm install -g cnpm --registry=https://registry.npm.tao ...

  2. andorid jar/库源码解析之okio

    目录:andorid jar/库源码解析 Okio: 作用: 说白了,就是一个IO库,基于java原生io.来进行操作,内部做了优化,简洁,高效.所以受到了一部分人的喜欢和使用 栗子: 读写文件. p ...

  3. restful 架构风格的curd(增删改查)

    restful架构 概念:REST指的是一组架构约束条件和原则,如果一个架构符合REST的约束条件和原则,就称之为RESTful架构. restful不是一个专门的技术,他是一个规范.规范就是写写代码 ...

  4. Java 函数式接口

    目录 Java 函数式接口 1. 函数式接口 1.1 概念 1.2 格式 1.3 函数式接口的使用 2. 函数式编程 2.1 Lambda的延迟执行 性能浪费的日志案例 使用Lambda表达式的优化 ...

  5. SpringCloud 踩坑之 注册中心绑定端口一直是8080

    今天在启动注册中心服务时,突然端口一直是8080,找了好久一直没找到原因,先看看我有问题的配置 spring: application: name: eureka-server profiles: d ...

  6. Spring Cloud feign GET请求无法用实体传参的解决方法

    代码如下: @FeignClient(name = "eureka-client", fallbackFactory = FallBack.class, decode404 = t ...

  7. MySQL 入门(3):事务隔离

    摘要 在这一篇内容中,我将从事务是什么开始,聊一聊事务的必要性. 然后,介绍一下在InnoDB中,四种不同级别的事务隔离,能解决什么问题,以及会带来什么问题. 最后,我会介绍一下InnoDB解决高并发 ...

  8. 字节码编程,Javassist篇三《使用Javassist在运行时重新加载类「替换原方法输出不一样的结果」》

    作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 通过前面两篇 javassist 的基本内容,大体介绍了:类池(ClassPool) ...

  9. 世界这么大,Python 也想去看看

    把时间线拉回到 2015 年 4 月 13 日,一位河南省实验中学的心里老师在辞职信上写下了「世界那么大,我想去看看」这句话,后来爆红网络,我想这位心里老师当时写这句话的时候肯定没想到这句只有十个字的 ...

  10. 简述SpringCloud框架

    1.什么是SpringCloud? SpringCloud是一系列框架的有序集合,它利用SpringBoot的开发便利性简化了分布式系统的开发,比如服务发现.服务网关.服务路由.链路追踪等.Sprin ...