在Web开发中表单的重复提交是很严重的问题,重复提交成功会产生垃圾数据消耗不必要的资源,更严重的是如果遇到恶意刷库的情况垃圾数据更是数不胜数。在正常使用过程中产生重复提交的情况也有多重情况:鼠标连击、回退提交、刷新提交、网络延迟用户重复提交等。
防止重复提交的方法分两大类就是客户端、服务端(这是废话了)。客户端主要是用js对按钮的限制,一次点击后屏蔽按钮或者是直接跳转等待页面,服务端思路为客户端加token进行验证。客户端就不做详细介绍,主要介绍服务端的控制。
1、客户端存储
就是在客户端不同的地方存储两个token,在服务端进行校验。在Form表单中存储一个token利用隐藏域,在Cookie中存储一个(也可以都放到form表单中两个不同的隐藏域)。档form表单提交的时候,对这两个token进行验证,相同则允许提交否则阻止提交。
优点:
不占用服务器资源
实施起来简单,易上手
缺点:
容易伪造(防君子不防小人)
占用网络资源(或许不是那么明显)
详细介绍一下客户端分布存储在Form表单中和Cookie中的情况。
客户端的实现如下:
packagecn.simple.token;importjavax.servlet.http.Cookie;importjavax.servlet.http.HttpServletRequest;importjavax.servlet.http.HttpServletResponse;importorg.apache.commons.lang.StringUtils;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.stereotype.Service;/*** 双客户端验证
*@authorldm
* @Date 2016年6月16日*/@Service("clientTokenProcesser")public class ClientTokenProcesser extendsTokenProcesser {
@Autowired
HttpServletResponse response;
@Overridepublic booleanvalidToken(HttpServletRequest request) {
String formToken=request.getParameter(getTokenField()).toString();
System.out.println("formToken:"+formToken);if(StringUtils.isEmpty(formToken))
{
printException("表单中没有token");return false;
}
Cookie[] cookies=request.getCookies();if(cookies==null)
{
printException("cookie 中没有token");
}for(Cookie cookie : cookies) {if(cookie.getName().equals(getTokenKey(request)))
{
String cookieValue=cookie.getValue();
System.out.println("cookieToken:"+cookieValue);if(cookieValue.equals(formToken))
{return true;
}
}
}return false;
}private voidprintException(String msg) {
Exception e= newRuntimeException(msg);
e.printStackTrace();
}
@OverridepublicString getTokenKey(HttpServletRequest request) {
String cookieKey= getTokenField() + "_cookie";returncookieKey;
}
@Overridepublic voidsaveToken(HttpServletRequest request) {
String token=MakeToken.getInstance().getToken();
request.setAttribute(getTokenField(), token);if (response == null) {throw new RuntimeException("HttpServletResponse is null");
}
Cookie cookie= newCookie(getTokenKey(request), token);
response.addCookie(cookie);
}
@OverridepublicString getClientToken(HttpServletRequest request) {
Object token=request.getParameter(getTokenField());if (token == null) {return null;
}else{returntoken.toString();
}
}
}
View Code
2、双向存储
客户端和服务端的token各自独立存储,客户端存储在Cookie或者Form的隐藏域(放在Form隐藏域中的时候,需要每个表单)中,服务端存储在Session(单机系统中可以使用)或者其他缓存系统(分布式系统可以使用)中。
优点:
安全性高(几乎是无法伪造的)
网络资源相对于前者有所减少
缺点:
整个系统实施起来较第一种方法的时候复杂度增加
详细介绍一下服务端存储在session中客户端存储在Cookie中
SessionTokenProcesser实现如下:
packagecn.simple.token;importjavax.servlet.http.Cookie;importjavax.servlet.http.HttpServletRequest;importjavax.servlet.http.HttpServletResponse;importjavax.servlet.http.HttpSession;importorg.apache.commons.lang.StringUtils;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.stereotype.Service;/*** 服务端用Session存储
*
*@authorldm
* @Date 2016年6月16日*/@Service("sessionTokenProcesser")public class SessionTokenProcesser extendsTokenProcesser {
@Autowired
HttpServletResponse response;
@Overridepublic booleanvalidToken(HttpServletRequest request) {
String clientToken=getClientToken(request);if(StringUtils.isEmpty(clientToken)) {return false;
}
HttpSession session= request.getSession(false);if (session == null) {return false;
}
String tokenKey=getTokenKey(request);
Object tokenObj=session.getAttribute(tokenKey);if(tokenObj==null)
{
rethrow("服务端不存在当前token,请重新请求表单");
}
String serverToken=tokenObj.toString();
session.removeAttribute(tokenKey);
System.out.println("remove server token:" +serverToken);returnclientToken.equals(serverToken);
}
@OverridepublicString getTokenKey(HttpServletRequest request) {returngetTokenField();
}
@Overridepublic voidsaveToken(HttpServletRequest request) {
HttpSession session=request.getSession();
String tokenKey=getTokenKey(request);
Object tokenObj=session.getAttribute(tokenKey);
String token;if (tokenObj == null) {
token=MakeToken.getInstance().getToken();//服务端保存token
session.setAttribute(tokenKey, token);
}else{
token=tokenObj.toString();
}
System.out.println("current token:" +token);//写入cookie
Cookie cookie = newCookie(getTokenField(), token);
response.addCookie(cookie);
}private voidrethrow(String message) {
RuntimeException e= newRuntimeException(message);throwe;
}
@OverridepublicString getClientToken(HttpServletRequest request) {
Cookie[] cookies=request.getCookies();if (cookies == null) {
rethrow("没有读取到客户端的cookie");return null;
}for(Cookie cookie : cookies) {if(cookie.getName().equals(getTokenKey(request))) {
String cookieValue=cookie.getValue();returncookieValue;
}
}
rethrow("客户端cookie中没有存储token");return null;
}
}
View Code