CSRF的概率可以参考:http://netsecurity.51cto.com/art/200812/102951.htm
本文介绍的是基于spring拦截器的Spring MVC实现
首先配置拦截器:
<mvc:interceptors> <mvc:interceptor> <!-- 匹配的是url路径, 如果不配置或/**,将拦截所有的Controller --> <mvc:mapping path="/xxx/**" /> <bean class="com.xxx.SecurityTokenInterceptor"></bean> </mvc:interceptor> <!-- 当设置多个拦截器时,先按顺序调用preHandle方法,然后逆序调用每个拦截器的postHandle和afterCompletion方法 --> </mvc:interceptors>
SecurityTokenInterceptor代码如下
import java.io.IOException; import java.lang.reflect.Method; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.util.StringUtils; import org.springframework.web.method.HandlerMethod; import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; /** * 防止重复提交过滤器 * */ public class SecurityTokenInterceptor extends HandlerInterceptorAdapter { private static final Logger duplicateAvoidLOG = LoggerFactory .getLogger(SecurityTokenInterceptor.class); public static final String DUPLICATEAVOID_TOKEN = "duplicateAvoid_token"; public static final String PAGE_DUPLICATEAVOID_TOKEN = "sumbit_token"; // private static final ConcurrentMap<String, String> tokenMap = new ConcurrentHashMap<String, String>(); public static final int expressTime = 60 * 60 * 24 * 5; public static final String TOKEN_ERROR_CODE ="duplicate_Error"; public static final ThreadLocal<String> threadtoken = new ThreadLocal<String>(); /** * 前置处理器中 检查 DuplicateAvoid注解 */ @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler){ duplicateAvoidLOG.info("DuplicateAvoidSubmitInterceptor start,[client:" + getRemoteHost(request) + ",url:" + request.getServletPath() + "]"); if (handler instanceof HandlerMethod) { HandlerMethod handlerMethod = (HandlerMethod) handler; Method method = handlerMethod .getMethod(); SecurityToken annotation = method.getAnnotation(SecurityToken.class); duplicateAvoidLOG.info("method infos,[method:" + method.getName() + "]"); if (annotation != null) { //需要验证token boolean needValidateToken = annotation.validateToken(); if(needValidateToken){ if(isRepeatSubmit(request)){ duplicateAvoidLOG.error("please don‘t repeat submit,[client:" + getRemoteHost(request) + ",url:" + request.getServletPath() + "]"); request.getSession().setAttribute(TOKEN_ERROR_CODE, "please don‘t repeat submit"); String fuction = annotation.ajaxFailCallBack(); String responStr = "{\"duplicate\":\"true\",\"callback\":\""+fuction+"\"}"; try { response.getOutputStream().write(responStr.getBytes()); response.getOutputStream().flush(); response.getOutputStream().close(); } catch (IOException e) { duplicateAvoidLOG.error("repeat submit ,[client:" + getRemoteHost(request) + ",url:" + request.getServletPath() + "]"); e.printStackTrace(); } return false; } request.getSession(false).removeAttribute(DUPLICATEAVOID_TOKEN); } //需要保存token,先产生token 保存到response的cookie中,服务器端保存在业务方法调用后保存 boolean needSaveToken = annotation.generateToken(); if (needSaveToken) { String token = UUID.randomUUID().toString(); Cookie tokenCookie = new Cookie(PAGE_DUPLICATEAVOID_TOKEN, token); tokenCookie.setMaxAge(expressTime); tokenCookie.setPath("/"); response.addCookie(tokenCookie); // response.addHeader(PAGE_DUPLICATEAVOID_TOKEN, token); threadtoken.set(token); } } } return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { duplicateAvoidLOG.info("DuplicateAvoidSubmitInterceptor postHandle,[client:" + getRemoteHost(request) + ",url:" + request.getServletPath() + "]"+",token ="+ threadtoken.get()); if (handler instanceof HandlerMethod) { HandlerMethod handlerMethod = (HandlerMethod) handler; Method method = handlerMethod .getMethod(); SecurityToken annotation = method.getAnnotation(SecurityToken.class); duplicateAvoidLOG.info("method infos,[method:" + method.getName() + "]"); if (annotation != null) { //保存token,到服务器的session中 boolean needSaveToken = annotation.generateToken(); if (needSaveToken) { String token = threadtoken.get(); threadtoken.set(""); if (!StringUtils.isEmpty(token)) { request.getSession().setAttribute(DUPLICATEAVOID_TOKEN, token); } } } } } private boolean isRepeatSubmit(HttpServletRequest request){ String serverToken = (String) request.getSession(false).getAttribute(DUPLICATEAVOID_TOKEN); Cookie cookies[]=request.getCookies(); Cookie tokenCookie=null; String clinetToken = null; for (int i = 0; i < cookies.length; i++) { tokenCookie = cookies[i]; if(PAGE_DUPLICATEAVOID_TOKEN.equals(tokenCookie.getName())){ clinetToken = tokenCookie.getValue(); } } if (StringUtils.isEmpty(clinetToken)) { clinetToken = request.getParameter(PAGE_DUPLICATEAVOID_TOKEN); } duplicateAvoidLOG.info("isRepeatSubmit ,[serverToken:" + serverToken + ",clinetToken:" + clinetToken + "]"); if (StringUtils.isEmpty(serverToken) || StringUtils.isEmpty(clinetToken)) { return true; } if (!serverToken.equals(clinetToken)) { return true; } return false; } private String getRemoteHost(HttpServletRequest request){ String ip = request.getHeader("x-forwarded-for"); if(ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)){ ip = request.getHeader("Proxy-Client-IP"); } if(ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)){ ip = request.getHeader("WL-Proxy-Client-IP"); } if(ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)){ ip = request.getRemoteAddr(); } return ip.equals("0:0:0:0:0:0:0:1")?"127.0.0.1":ip; } }
SecurityToken.java
import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.springframework.beans.factory.annotation.Required; /** * <p> * 防止重复提交注解,用于方法上<br/> * 在control方法上,设置needSaveToken()为true,此时拦截器会在Session中保存一个token<br/> * 在control方法上,设置needValidateToken()为true,此时拦截器会在Session中验证token,并且删除token<br/> * 需要防止重复提交的页面中需要添加<br/> * </p> * */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface SecurityToken { boolean generateToken() default false; boolean validateToken() default false; //ajax请求如果重复提交后默认的JS回调方法 String ajaxFailCallBack() default "HandleTokenFail"; }
使用方法:
在跳转到需要防止重复提交的页面的controller上加注解来generateToken, 然后在业务方法的controller上加入注解validateToken(如果此方法跳转(或者ajax)执行完后下面的页面还要继续防重复可以在此方法上再加generateToken)
防止跨站请求伪造(CSRF)攻击 和 防重复提交 的方法的实现
原文:http://www.cnblogs.com/yangzhilong/p/4917840.html