1.针对的表单重复提交一般场景
- 客户端浏览器F5刷新
- 重复点击提交按钮
2.效果
总体效果就是,第一次输入内容,可以正常提交,之后,F5以及重复点击提交按钮都是无效的.但是,在输入新内容之后,也可以正常提交.
3.Session令牌
session令牌(token)
只是一个一次性的通行证性质的字符串.
甚至可以使用一个字符去表示这个令牌,但是令牌在短时间内不允许出现重复.
生成
可以使用 系统时间+随机数—–>成摘—–>字符串(最终结果) 的形式
4.流程
流程已很明白,可以参考7帮助理解
5.token的生成
使用了加密工具,任何文本获取的摘要长度都是一样的
package utils;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
import java.util.Random;
public class TokenUtils {
/**
*@Author: zsb
*@Description: 获取一个令牌
*@params:
*@Date: 2018/6/13
*/
public static String getToken(){
//获得一个未加密的文本
String tokenStr = System.currentTimeMillis() + new Random().nextInt(999999) + "";
String result = null;
try {
//使用md5加密
MessageDigest messageDigest = MessageDigest.getInstance("md5");
//更新摘要
messageDigest.update(tokenStr.getBytes());
//计算摘要
byte token[] = messageDigest.digest();
//将字节数据转换为字符串,base64就是一个字节转字符串的工具
result = Base64.getEncoder().encodeToString(token);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return result;
}
}
6.Servlet中service方法代码
//数据获取请求数据---------------------------------------------------------------------------
//
//token数据及表单提交处理---------------------------------------------------------------------
//如果session里没有token,则生成一个,通常是第一次访问时需要,
//此后session内都会保存着一个token,每次表单成功提交,session里的token也会更新
String sessionToken = (String)req.getSession().getAttribute("token");
if(sessionToken == null){
String token = TokenUtils.getToken(); //获得一个token
//将令牌传入session,jsp页面中通过el表达式获取session域中的token
req.getSession().setAttribute("token",token);
} else {
//获取表单提交的token,注意,在F5刷新时,也只会获取前一次成功提交的token,因此不怕F5
String jspToken= req.getParameter("token");
System.out.println("获得的隐藏域中token值: " + jspToken);
System.out.println("Session域中的token值: " + sessionToken);
if(jspToken != null){ //只需验证jsp获得的token不为null即可
if(sessionToken.equals(sessionToken)){ //若两个token相同,则令牌验证通过
System.out.println("令牌验证通过");
//就算令牌通过,空内容的提交也是不允许的,这是第二道防表单重复提交屏障
if(StringUtils.strNotEmptyNotNull(name) && StringUtils.strNotEmptyNotNull(content)){
//数据库操作
System.out.println("数据库操作中");
//更新令牌,流程图里写删除,这里因为可以覆盖所以就不需要删除了,直接重新生成覆盖旧的token值
String token = TokenUtils.getToken(); //获得一个令牌
req.getSession().setAttribute("token",token);
}
} else{
System.out.println("当前jspToken: "+ jspToken +
"当前SessionToken: " + sessionToken +
"请不要重复提交表单");
}
}
}
7.过程描述以及实现原理讲述
1.当我们第一次进入这个含有表单的页面的时候,会自动获取一个token,并将token存储在session中,因为一般还有其他页面数据获取操作,所以需要使用转发.页面转发完成后,jsp页面的隐藏域中会自动获取这个token,这时候我们向表单里填入数据,然后提交,可以正常提交.提交完成后,session里的token被更新,最后再次转发到了这个jsp页面
2.我们点击F5,对页面进行刷新,发现不会重复提交,这是因为,我们一直使用的是转发,request里jsp隐藏域的token其实还是上一次的数据,而session中的token在上一次成功提交后就已经被更新了,并且每次验证的时候,获取的都是session里最新的token,因此二者不会相等,令牌验证失败,也就不会进行提交操作.这一点需要注意
3.当我们再点提交按钮时,由于这是一次新的请求,因此会把最新的jsp会把最新的session里的token带入请求信息里,同样,后台servlet里也是最新的token,因此,这个时候可以通过令牌验证,但是,由于每次提交后,表单内容被清空,但是我们不允许空内容的表单,因此这个时候表单提交不会生效
4.当我们输入新内容后,再点击提交,同样可以提交.令牌验证与3相同,而且有了内容,因此可以进行提交操作,提交后,session的token被更新,同样,jsp的token也会被更新,但是,我们获取的是request里的表单的token,这个因为是转发的关系,request里的token是上一次的内容.
关键点:F5刷新的时候,request里的token是上一次成功提交的token