CsrfTokenManager 用于管理csrfToken相关
package com.web.common;
import java.util.UUID;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
public final class CsrfTokenManager {
// 隐藏域参数名称
static final String CSRF_PARAM_NAME = "CSRFToken";
// session中csrfToken参数名称
public static final String CSRF_TOKEN_FOR_SESSION_ATTR_NAME = CsrfTokenManager.class
.getName() + ".tokenval";
private CsrfTokenManager() {
};
// 在session中创建csrfToken
public static String createTokenForSession(HttpSession session) {
String token = null;
synchronized (session) {
token = (String) session
.getAttribute(CSRF_TOKEN_FOR_SESSION_ATTR_NAME);
if (null == token) {
token = UUID.randomUUID().toString();
session.setAttribute(CSRF_TOKEN_FOR_SESSION_ATTR_NAME, token);
}
}
return token;
}
public static String getTokenFromRequest(HttpServletRequest request) {
return request.getParameter(CSRF_PARAM_NAME);
}
}
CsrfRequestDataValueProcessor 自动创建hidden的csrfToken域的类 package com.web.common;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.support.RequestDataValueProcessor;
import com.google.common.collect.Maps;
@Component("requestDataValueProcessor")
public class CsrfRequestDataValueProcessor implements RequestDataValueProcessor {
@Override
public String processAction(HttpServletRequest request, String action) {
// TODO 暂时原样返回action
return action;
}
@Override
public String processFormFieldValue(HttpServletRequest request,
String name, String value, String type) {
// TODO 暂时原样返回value
return value;
}
@Override
public Map getExtraHiddenFields(HttpServletRequest request) {
//此处是当使用spring的taglib标签
创建表单时候,增加的隐藏域参数Map hiddenFields = Maps.newHashMap();
hiddenFields.put(CsrfTokenManager.CSRF_PARAM_NAME,
CsrfTokenManager.createTokenForSession(request.getSession()));
return hiddenFields;
}
@Override
public String processUrl(HttpServletRequest request, String url) {
// TODO 暂时原样返回url
return url;
}
}
CsrfInterceptor 对于post请求进行拦截,检测csrfToken是否匹配
package com.web.security;
import java.net.URLEncoder;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import com.web.common.CsrfTokenManager;
import com.web.common.WebUser;
public class CsrfInterceptor extends HandlerInterceptorAdapter {
private static final Logger logger = LoggerFactory
.getLogger(CsrfInterceptor.class);
@Autowired
WebUser webUser;
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
if ("POST".equalsIgnoreCase(request.getMethod())) {
String CsrfToken = CsrfTokenManager.getTokenFromRequest(request);
if (CsrfToken == null
|| !CsrfToken.equals(request.getSession().getAttribute(
CsrfTokenManager.CSRF_TOKEN_FOR_SESSION_ATTR_NAME))) {
String reLoginUrl = "/login?backurl="
+ URLEncoder.encode(getCurrentUrl(request), "utf-8");
response.sendRedirect(reLoginUrl);
return false;
}
}
return true;
}
private String getCurrentUrl(HttpServletRequest request) {
String currentUrl = request.getRequestURL().toString();
if (!StringUtils.isEmpty(request.getQueryString())) {
currentUrl += "?" + request.getQueryString();
}
return currentUrl;
}
}
springMVC 配置文件,增加需要进行拦截的url
jsp页面,需要注意的是必须使用spring的form标签
标题:
jsp页面 查看源码会发现已经添加上了hidden字段
标题:
1.只有当使用spring的form标签时候,才会在渲染jsp页面时候触发
org.springframework.web.servlet.tags.form.FormTag 类中的
@Override
public int doEndTag() throws JspException {
RequestDataValueProcessor processor = getRequestContext().getRequestDataValueProcessor();
ServletRequest request = this.pageContext.getRequest();
if ((processor != null) && (request instanceof HttpServletRequest)) {
writeHiddenFields(processor.getExtraHiddenFields((HttpServletRequest) request));
}
this.tagWriter.endTag();
return EVAL_PAGE;
}
该方法会调用上文中所说的CsrfRequestDataValueProcessor中的创建隐藏域的方法getExtraHiddenFields来创建csrfToken隐藏域。
2.对相关需要进行防范csrf攻击的post请求地址进行拦截,此处是依赖springMVC中提供的拦截器机制来操作的
当用户访问"/forum/post"这个请求时候,会直接进入CsrfInterceptor的preHandle方法,此处针对post请求进行了判断,如果从request中获取的csrfToken不存在或者与session中的csrfToken不相匹配,那么可以不进行后续流程,至于返回404还是返回到登录页面,就随便作者了。