转自:http://www.cnblogs.com/xdp-gacl/p/3859416.html

利用Session防止表单重复提交

  对于【场景二】和【场景三】导致表单重复提交的问题,既然客户端无法解决,那么就在服务器端解决,在服务器端解决就需要用到session了。

  具体的做法:在服务器端生成一个唯一的随机标识号,专业术语称为Token(令牌),同时在当前用户的Session域中保存这个Token。然后将Token发送到客户端的Form表单中,在Form表单中使用隐藏域来存储这个Token,表单提交的时候连同这个Token一起提交到服务器端,然后在服务器端判断客户端提交上来的Token与服务器端生成的Token是否一致,如果不一致,那就是重复提交了,此时服务器端就可以不处理重复提交的表单。如果相同则处理表单提交,处理完后清除当前用户的Session域中存储的标识号。
  在下列情况下,服务器程序将拒绝处理用户提交的表单请求:

  1. 存储Session域中的Token(令牌)与表单提交的Token(令牌)不同。
  2. 当前用户的Session中不存在Token(令牌)
  3. 用户提交的表单数据中没有Token(令牌)

看具体的范例:

  1.创建FormServlet,用于生成Token(令牌)和跳转到form.jsp页面

 1 package xdp.gacl.session;
2
3 import java.io.IOException;
4 import javax.servlet.ServletException;
5 import javax.servlet.http.HttpServlet;
6 import javax.servlet.http.HttpServletRequest;
7 import javax.servlet.http.HttpServletResponse;
8
9 public class FormServlet extends HttpServlet {
10 private static final long serialVersionUID = -884689940866074733L;
11
12 public void doGet(HttpServletRequest request, HttpServletResponse response)
13 throws ServletException, IOException {
14
15 String token = TokenProccessor.getInstance().makeToken();//创建令牌
16 System.out.println("在FormServlet中生成的token:"+token);
17 request.getSession().setAttribute("token", token); //在服务器使用session保存token(令牌)
18 request.getRequestDispatcher("/form.jsp").forward(request, response);//跳转到form.jsp页面
19 }
20
21 public void doPost(HttpServletRequest request, HttpServletResponse response)
22 throws ServletException, IOException {
23 doGet(request, response);
24 }
25
26 }

  2.在form.jsp中使用隐藏域来存储Token(令牌)

 1 <%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
2 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
3 <html>
4 <head>
5 <title>form表单</title>
6 </head>
7
8 <body>
9 <form action="${pageContext.request.contextPath}/servlet/DoFormServlet" method="post">
10 <%--使用隐藏域存储生成的token--%>
11 <%--
12 <input type="hidden" name="token" value="<%=session.getAttribute("token") %>">
13 --%>
14 <%--使用EL表达式取出存储在session中的token--%>
15 <input type="hidden" name="token" value="${token}"/>
16 用户名:<input type="text" name="username">
17 <input type="submit" value="提交">
18 </form>
19 </body>
20 </html>

  3.DoFormServlet处理表单提交

 1 package xdp.gacl.session;
2
3 import java.io.IOException;
4 import javax.servlet.ServletException;
5 import javax.servlet.http.HttpServlet;
6 import javax.servlet.http.HttpServletRequest;
7 import javax.servlet.http.HttpServletResponse;
8
9 public class DoFormServlet extends HttpServlet {
10
11 public void doGet(HttpServletRequest request, HttpServletResponse response)
12 throws ServletException, IOException {
13
14 boolean b = isRepeatSubmit(request);//判断用户是否是重复提交
15 if(b==true){
16 System.out.println("请不要重复提交");
17 return;
18 }
19 request.getSession().removeAttribute("token");//移除session中的token
20 System.out.println("处理用户提交请求!!");
21 }
22
23 /**
24 * 判断客户端提交上来的令牌和服务器端生成的令牌是否一致
25 * @param request
26 * @return
27 * true 用户重复提交了表单
28 * false 用户没有重复提交表单
29 */
30 private boolean isRepeatSubmit(HttpServletRequest request) {
31 String client_token = request.getParameter("token");
32 //1、如果用户提交的表单数据中没有token,则用户是重复提交了表单
33 if(client_token==null){
34 return true;
35 }
36 //取出存储在Session中的token
37 String server_token = (String) request.getSession().getAttribute("token");
38 //2、如果当前用户的Session中不存在Token(令牌),则用户是重复提交了表单
39 if(server_token==null){
40 return true;
41 }
42 //3、存储在Session中的Token(令牌)与表单提交的Token(令牌)不同,则用户是重复提交了表单
43 if(!client_token.equals(server_token)){
44 return true;
45 }
46
47 return false;
48 }
49
50 public void doPost(HttpServletRequest request, HttpServletResponse response)
51 throws ServletException, IOException {
52 doGet(request, response);
53 }
54
55 }

  生成Token的工具类TokenProccessor

 1 package xdp.gacl.session;
2
3 import java.security.MessageDigest;
4 import java.security.NoSuchAlgorithmException;
5 import java.util.Random;
6 import sun.misc.BASE64Encoder;
7
8 public class TokenProccessor {
9
10 /*
11 *单例设计模式(保证类的对象在内存中只有一个)
12 *1、把类的构造函数私有
13 *2、自己创建一个类的对象
14 *3、对外提供一个公共的方法,返回类的对象
15 */
16 private TokenProccessor(){}
17
18 private static final TokenProccessor instance = new TokenProccessor();
19
20 /**
21 * 返回类的对象
22 * @return
23 */
24 public static TokenProccessor getInstance(){
25 return instance;
26 }
27
28 /**
29 * 生成Token
30 * Token:Nv6RRuGEVvmGjB+jimI/gw==
31 * @return
32 */
33 public String makeToken(){ //checkException
34 // 7346734837483 834u938493493849384 43434384
35 String token = (System.currentTimeMillis() + new Random().nextInt(999999999)) + "";
36 //数据指纹 128位长 16个字节 md5
37 try {
38 MessageDigest md = MessageDigest.getInstance("md5");
39 byte md5[] = md.digest(token.getBytes());
40 //base64编码--任意二进制编码明文字符 adfsdfsdfsf
41 BASE64Encoder encoder = new BASE64Encoder();
42 return encoder.encode(md5);
43 } catch (NoSuchAlgorithmException e) {
44 throw new RuntimeException(e);
45 }
46 }
47 }

利用Session防止表单重复提交

  对于【场景二】和【场景三】导致表单重复提交的问题,既然客户端无法解决,那么就在服务器端解决,在服务器端解决就需要用到session了。

  具体的做法:在服务器端生成一个唯一的随机标识号,专业术语称为Token(令牌),同时在当前用户的Session域中保存这个Token。然后将Token发送到客户端的Form表单中,在Form表单中使用隐藏域来存储这个Token,表单提交的时候连同这个Token一起提交到服务器端,然后在服务器端判断客户端提交上来的Token与服务器端生成的Token是否一致,如果不一致,那就是重复提交了,此时服务器端就可以不处理重复提交的表单。如果相同则处理表单提交,处理完后清除当前用户的Session域中存储的标识号。
  在下列情况下,服务器程序将拒绝处理用户提交的表单请求:

  1. 存储Session域中的Token(令牌)与表单提交的Token(令牌)不同。
  2. 当前用户的Session中不存在Token(令牌)
  3. 用户提交的表单数据中没有Token(令牌)

看具体的范例:

  1.创建FormServlet,用于生成Token(令牌)和跳转到form.jsp页面

 1 package xdp.gacl.session;
2
3 import java.io.IOException;
4 import javax.servlet.ServletException;
5 import javax.servlet.http.HttpServlet;
6 import javax.servlet.http.HttpServletRequest;
7 import javax.servlet.http.HttpServletResponse;
8
9 public class FormServlet extends HttpServlet {
10 private static final long serialVersionUID = -884689940866074733L;
11
12 public void doGet(HttpServletRequest request, HttpServletResponse response)
13 throws ServletException, IOException {
14
15 String token = TokenProccessor.getInstance().makeToken();//创建令牌
16 System.out.println("在FormServlet中生成的token:"+token);
17 request.getSession().setAttribute("token", token); //在服务器使用session保存token(令牌)
18 request.getRequestDispatcher("/form.jsp").forward(request, response);//跳转到form.jsp页面
19 }
20
21 public void doPost(HttpServletRequest request, HttpServletResponse response)
22 throws ServletException, IOException {
23 doGet(request, response);
24 }
25
26 }

  2.在form.jsp中使用隐藏域来存储Token(令牌)

 1 <%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
2 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
3 <html>
4 <head>
5 <title>form表单</title>
6 </head>
7
8 <body>
9 <form action="${pageContext.request.contextPath}/servlet/DoFormServlet" method="post">
10 <%--使用隐藏域存储生成的token--%>
11 <%--
12 <input type="hidden" name="token" value="<%=session.getAttribute("token") %>">
13 --%>
14 <%--使用EL表达式取出存储在session中的token--%>
15 <input type="hidden" name="token" value="${token}"/>
16 用户名:<input type="text" name="username">
17 <input type="submit" value="提交">
18 </form>
19 </body>
20 </html>

  3.DoFormServlet处理表单提交

 1 package xdp.gacl.session;
2
3 import java.io.IOException;
4 import javax.servlet.ServletException;
5 import javax.servlet.http.HttpServlet;
6 import javax.servlet.http.HttpServletRequest;
7 import javax.servlet.http.HttpServletResponse;
8
9 public class DoFormServlet extends HttpServlet {
10
11 public void doGet(HttpServletRequest request, HttpServletResponse response)
12 throws ServletException, IOException {
13
14 boolean b = isRepeatSubmit(request);//判断用户是否是重复提交
15 if(b==true){
16 System.out.println("请不要重复提交");
17 return;
18 }
19 request.getSession().removeAttribute("token");//移除session中的token
20 System.out.println("处理用户提交请求!!");
21 }
22
23 /**
24 * 判断客户端提交上来的令牌和服务器端生成的令牌是否一致
25 * @param request
26 * @return
27 * true 用户重复提交了表单
28 * false 用户没有重复提交表单
29 */
30 private boolean isRepeatSubmit(HttpServletRequest request) {
31 String client_token = request.getParameter("token");
32 //1、如果用户提交的表单数据中没有token,则用户是重复提交了表单
33 if(client_token==null){
34 return true;
35 }
36 //取出存储在Session中的token
37 String server_token = (String) request.getSession().getAttribute("token");
38 //2、如果当前用户的Session中不存在Token(令牌),则用户是重复提交了表单
39 if(server_token==null){
40 return true;
41 }
42 //3、存储在Session中的Token(令牌)与表单提交的Token(令牌)不同,则用户是重复提交了表单
43 if(!client_token.equals(server_token)){
44 return true;
45 }
46
47 return false;
48 }
49
50 public void doPost(HttpServletRequest request, HttpServletResponse response)
51 throws ServletException, IOException {
52 doGet(request, response);
53 }
54
55 }

  生成Token的工具类TokenProccessor

 1 package xdp.gacl.session;
2
3 import java.security.MessageDigest;
4 import java.security.NoSuchAlgorithmException;
5 import java.util.Random;
6 import sun.misc.BASE64Encoder;
7
8 public class TokenProccessor {
9
10 /*
11 *单例设计模式(保证类的对象在内存中只有一个)
12 *1、把类的构造函数私有
13 *2、自己创建一个类的对象
14 *3、对外提供一个公共的方法,返回类的对象
15 */
16 private TokenProccessor(){}
17
18 private static final TokenProccessor instance = new TokenProccessor();
19
20 /**
21 * 返回类的对象
22 * @return
23 */
24 public static TokenProccessor getInstance(){
25 return instance;
26 }
27
28 /**
29 * 生成Token
30 * Token:Nv6RRuGEVvmGjB+jimI/gw==
31 * @return
32 */
33 public String makeToken(){ //checkException
34 // 7346734837483 834u938493493849384 43434384
35 String token = (System.currentTimeMillis() + new Random().nextInt(999999999)) + "";
36 //数据指纹 128位长 16个字节 md5
37 try {
38 MessageDigest md = MessageDigest.getInstance("md5");
39 byte md5[] = md.digest(token.getBytes());
40 //base64编码--任意二进制编码明文字符 adfsdfsdfsf
41 BASE64Encoder encoder = new BASE64Encoder();
42 return encoder.encode(md5);
43 } catch (NoSuchAlgorithmException e) {
44 throw new RuntimeException(e);
45 }
46 }
47 }

补充:base64

Base64是网络上最常见的用于传输8Bit字节码的编码方式之一,Base64就是一种基于64个可打印字符来表示二进制数据的方法。可查看RFC2045~RFC2049,上面有MIME的详细规范。
Base64编码是从二进制到字符的过程,可用于在HTTP环境下传递较长的标识信息。例如,在Java Persistence系统Hibernate中,就采用了Base64来将一个较长的唯一标识符(一般为128-bit的UUID)编码为一个字符串,用作HTTP表单和HTTP GET URL中的参数。在其他应用程序中,也常常需要把二进制数据编码为适合放在URL(包括隐藏表单域)中的形式。此时,采用Base64编码具有不可读性,需要解码后才能阅读。

如:

转码过程例子:
3*8=4*6
内存1个字节占8位
转前: s 1 3
先转成ascii:对应 115 49 51
2进制: 01110011 00110001 00110011
(将十进制转成二进制方法:除2,写出每一步的余数,将余数倒过来,高位补0(凑够8位))
6个一组(4组) 011100110011000100110011
然后才有后面的 011100 110011 000100 110011
然后计算机是8位8位的存数 6不够,自动就补两个高位0了
所有有了 高位补0
科学计算器输入 00011100 00110011 00000100 00110011
得到 28 51 4 51
查对下照表 c z E z
先以“迅雷下载”为例: 很多下载类网站都提供“迅雷下载”的链接,其地址通常是加密的迅雷专用下载地址。
其实迅雷的“专用地址”也是用Base64"加密"的,其过程如下:
一、在地址的前后分别添加AA和ZZ
二、对新的字符串进行Base64编码
另: Flashget的与迅雷类似,只不过在第一步时加的“料”不同罢了,Flashget在地址前后加的“料”是[FLASHGET]
而QQ旋风的干脆不加料,直接就对地址进行Base64编码了
 

应用

Base64编码可用于在HTTP环境下传递较长的标识信息。例如,在Java Persistence系统Hibernate中,就采用了Base64来将一个较长的唯一标识符(一般为128-bit的UUID)编码为一个字符串,用作HTTP表单和HTTP GET URL中的参数。在其他应用程序中,也常常需要把二进制数据编码为适合放在URL(包括隐藏表单域)中的形式。此时,采用Base64编码不仅比较简短,同时也具有不可读性,即所编码的数据不会被人用肉眼所直接看到。

然而,标准的Base64并不适合直接放在URL里传输,因为URL编码器会把标准Base64中的“/”和“+”字符变为形如“%XX”的形式,而这些“%”号在存入数据库时还需要再进行转换,因为ANSI SQL中已将“%”号用作通配符。
为解决此问题,可采用一种用于URL的改进Base64编码,它不仅在末尾去掉填充的'='号,并将标准Base64中的“+”和“/”分别改成了“-”和“_”,这样就免去了在URL编解码和数据库存储时所要作的转换,避免了编码信息长度在此过程中的增加,并统一了数据库、表单等处对象标识符的格式。

另有一种用于正则表达式的改进Base64变种,它将“+”和“/”改成了“!”和“-”,因为“+”,“*”以及前面在IRCu中用到的“[”和“]”在正则表达式中都可能具有特殊含义。
此外还有一些变种,它们将“+/”改为“_-”或“._”(用作编程语言中的标识符名称)或“.-”(用于XML中的Nmtoken)甚至“_:”(用于XML中的Name)。
Base64要求把每三个8Bit的字节转换为四个6Bit的字节(3*8 = 4*6 = 24),然后把6Bit再添两位高位0,组成四个8Bit的字节,也就是说,转换后的字符串理论上将要比原来的长1/3。

规则

关于这个编码的规则:
①.把3个字符变成4个字符。
②每76个字符加一个换行符。
③.最后的结束符也要处理。
 

例子:

转换前 11111111, 11111111, 11111111 (二进制)
转换后 00111111, 00111111, 00111111, 00111111 (二进制)
上面的三个字节是原文,下面的四个字节是转换后的Base64编码,其前两位均为0。
转换后,我们用一个码表来得到我们想要的字符串(也就是最终的Base64编码),这个表是这样的:(摘自RFC2045)
 
转换表
Table 1: The Base64 Alphabet
索引
对应字符
索引
对应字符
索引
对应字符
索引
对应字符
0
A
17
R
34
i
51
z
1
B
18
S
35
j
52
0
2
C
19
T
36
k
53
1
3
D
20
U
37
l
54
2
4
E
21
V
38
m
55
3
5
F
22
W
39
n
56
4
6
G
23
X
40
o
57
5
7
H
24
Y
41
p
58
6
8
I
25
Z
42
q
59
7
9
J
26
a
43
r
60
8
10
K
27
b
44
s
61
9
11
L
28
c
45
t
62
+
12
M
29
d
46
u
63
/
13
N
30
e
47
v
   
14
O
31
f
48
w
   
15
P
32
g
49
x
   
16
Q
33
h
50
y
 

留点什么

利用session防止表单重复提交的更多相关文章

  1. java web学习总结(十三) -------------------使用Session防止表单重复提交

    在平时开发中,如果网速比较慢的情况下,用户提交表单后,发现服务器半天都没有响应,那么用户可能会以为是自己没有提交表单,就会再点击提交按钮重复提交表单,我们在开发中必须防止表单重复提交. 一.表单重复提 ...

  2. JavaWeb---总结(十三)使用Session防止表单重复提交

    在平时开发中,如果网速比较慢的情况下,用户提交表单后,发现服务器半天都没有响应,那么用户可能会以为是自己没有提交表单,就会再点击提交按钮重复提交表单,我们在开发中必须防止表单重复提交. 一.表单重复提 ...

  3. [Java拾遗五]使用Session防止表单重复提交

    申明:此文章属于转载, 转自博客: http://www.cnblogs.com/xdp-gacl/p/3859416.html在平时开发中,如果网速比较慢的情况下,用户提交表单后,发现服务器半天都没 ...

  4. java web 学习十三(使用session防止表单重复提交)

    在平时开发中,如果网速比较慢的情况下,用户提交表单后,发现服务器半天都没有响应,那么用户可能会以为是自己没有提交表单,就会再点击提交按钮重复提交表单,我们在开发中必须防止表单重复提交. 一.表单重复提 ...

  5. JavaWeb学习总结(十三)——使用Session防止表单重复提交

    在平时开发中,如果网速比较慢的情况下,用户提交表单后,发现服务器半天都没有响应,那么用户可能会以为是自己没有提交表单,就会再点击提交按钮重复提交表单,我们在开发中必须防止表单重复提交. 一.表单重复提 ...

  6. (转)JavaWeb学习总结(十三)——使用Session防止表单重复提交

    如何防止表单重复提交 在平时开发中,如果网速比较慢的情况下,用户提交表单后,发现服务器半天都没有响应,那么用户可能会以为是自己没有提交表单,就会再点击提交按钮重复提交表单,我们在开发中必须防止表单重复 ...

  7. 使用Session防止表单重复提交

    本文出处:http://www.cnblogs.com/xdp-gacl/p/3859416.html 在平时开发中,如果网速比较慢的情况下,用户提交表单后,发现服务器半天都没有响应,那么用户可能会以 ...

  8. javaWeb学习总结(7)- 使用Session防止表单重复提交

    在平时开发中,如果网速比较慢的情况下,用户提交表单后,发现服务器半天都没有响应,那么用户可能会以为是自己没有提交表单,就会再点击提交按钮重复提交表单,我们在开发中必须防止表单重复提交. 一.表单重复提 ...

  9. JavaWeb学习 (十二)————使用Session防止表单重复提交

    在平时开发中,如果网速比较慢的情况下,用户提交表单后,发现服务器半天都没有响应,那么用户可能会以为是自己没有提交表单,就会再点击提交按钮重复提交表单,我们在开发中必须防止表单重复提交. 一.表单重复提 ...

随机推荐

  1. 配置 -- php运行报Call to undefined function curl_init()的解决办法

    很早之前就出现过这个问题,网上百度了下,答案千篇一律,都是说: 1.在php.ini中开启curl扩展 2.将php目录下的libeay32.dll.ssleay32.dll.php5ts.dll拷贝 ...

  2. input 手机号

    关于input手机号的验证 一.手机号的判断方法: function isPhoneTel(n){ var reg = /^1[3|4|5|8]\d{9}$/; if(!!(reg.test(n))) ...

  3. python mysql redis mongodb selneium requests二次封装为什么大都是使用类的原因,一点见解

    1.python mysql  redis mongodb selneium requests举得这5个库里面的主要被用户使用的东西全都是面向对象的,包括requests.get函数是里面每次都是实例 ...

  4. [Bayes] Understanding Bayes: Updating priors via the likelihood

    From: https://alexanderetz.com/2015/07/25/understanding-bayes-updating-priors-via-the-likelihood/ Re ...

  5. [React] 16 - Topic: Mixins & Higher-Order Components

    到底是个什么东东,待学习中... Ref: https://www.cnblogs.com/wonyun/p/5930333.html

  6. INTERVAL

    select sysdate - interval '10' day as "10天前",       sysdate - interval '10' hour as " ...

  7. Android中使用第三方jar包

    步骤: 方法1:Eclipse下, 右键工程, Build path, java build path, 选择libraries 在右边的按钮中点击“Add Library” 选择“User libr ...

  8. Android开发训练之第五章第六节——Transferring Data Using Sync Adapters

    Transferring Data Using Sync Adapters GET STARTED DEPENDENCIES AND PREREQUISITES Android 2.1 (API Le ...

  9. 10.4ORM回顾!

    2018-10-4 17:41:52 继续优化一下我的博客项目!! 贴上orm参考连接:https://www.cnblogs.com/yuanchenqi/articles/8963244.html ...

  10. Diagnostics: File file:/tmp/spark-***/__spark_libs__***.zip does not exist

    Diagnostics: File file:/tmp/spark-c03df206-c90e-4c97-a2d6-a5d3fdb17811/__spark_libs__303213348409500 ...