Spring3.x通过配置来防范csrf攻击

2014-11-12 17:43:00
admin
原创 3911
摘要:自己在做一个小的web项目,用的是spring3.x,正好在做安全性方面的一些改进时候,需要进行xss和csrf的简单防范,上网上搜了一下,可以通过spring的几个简单配置来实现这点。

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<String, String> getExtraHiddenFields(HttpServletRequest request) {
        //此处是当使用spring的taglib标签<form:form>创建表单时候,增加的隐藏域参数
        Map<String, String> 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
<!-- 安全拦截用的 -->
<mvc:interceptors>
	<mvc:interceptor>
		<mvc:mapping path="/forum/post" />
		<mvc:mapping path="/forum/reply/*" />
		<bean class="com.uncle5.pubrub.web.security.CsrfInterceptor" />
	</mvc:interceptor>        
</mvc:interceptors>
jsp页面,需要注意的是必须使用spring的form标签
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%>
<div style="margin: 0 auto; border: 1px solid orange; width: 80%; height: 400px;">
	<form:form action="/forum/post" method="post">
		<span>标题:</span><input type="text" name="title" id="title">
		<textarea name="content" id="content" rows="30" cols="40"></textarea>
		<input type="submit" name="submit" value="submit">
	</form:form>
</div>
jsp页面 查看源码会发现已经添加上了hidden字段
<div style="margin: 0 auto; border: 1px solid orange; width: 80%; height: 400px;">
    <form id="command" action="/forum/post" method="post">
		<span>标题:</span><input type="text" name="title" id="title">
		<textarea name="content" id="content" rows="30" cols="40"></textarea>           
		<input type="submit" name="submit" value="submit">
        <input type="hidden" name="CSRFToken" value="35afec82-da7b-449e-9ae9-b38664b5af63" />
	</form>
</div>

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中提供的拦截器机制来操作的
<mvc:interceptors>
        <mvc:interceptor>
            <mvc:mapping path="/forum/post" />
            <mvc:mapping path="/forum/reply/*" />
            <bean class="com.uncle5.pubrub.web.security.CsrfInterceptor" />
        </mvc:interceptor>
    </mvc:interceptors>
当用户访问"/forum/post"这个请求时候,会直接进入CsrfInterceptor的preHandle方法,此处针对post请求进行了判断,如果从request中获取的csrfToken不存在或者与session中的csrfToken不相匹配,那么可以不进行后续流程,至于返回404还是返回到登录页面,就随便作者了。


博客分类
© 2012-2021    豫ICP备12018026号-1 SQL查询:5
内存占用:7.00MB
PHP 执行时间:0.06