利用spring-security解决CSRF问题

CSRF介绍

CSRF(Cross-site request forgery),中文名称:跨站请求伪造,也被称为:one click attack/session riding,缩写为:CSRF/XSRF。
具体SCRF的介绍和攻击方式请参看百度百科的介绍和一位大牛的分析:
CSRF百度百科
浅谈CSRF攻击方式

配置步骤

1.依赖jar包


        4.2.2.RELEASE
    

                org.springframework.security
                spring-security-core
                ${spring.security.version}
            

            
                org.springframework.security
                spring-security-web
                ${spring.security.version}
            

            
                org.springframework.security
                spring-security-config
                ${spring.security.version}
            

2.web.xml配置


        springSecurityFilterChain
        org.springframework.web.filter.DelegatingFilterProxy
    

    
        springSecurityFilterChain
        /*
    

3.Spring配置文件配置



    
        
            
        
        
    

4.自定义RequestMatcher的实现类CsrfSecurityRequestMatcher

这个类被用来自定义哪些请求是不需要进行拦截过滤的。如果配置csrf,所有http请求都被会CsrfFilter拦截,而CsrfFilter中有一个私有类DefaultRequiresCsrfMatcher。
源码1:DefaultRequiresCsrfMatcher类
private static final class DefaultRequiresCsrfMatcher implements RequestMatcher {
        private final HashSet allowedMethods;

        private DefaultRequiresCsrfMatcher() {
            this.allowedMethods = new HashSet(Arrays.asList(new String[]{"GET", "HEAD", "TRACE", "OPTIONS"}));
        }

        public boolean matches(HttpServletRequest request) {
            return !this.allowedMethods.contains(request.getMethod());
        }
    }
从这段源码可以发现,POST方法被排除在外了,也就是说只有GET|HEAD|TRACE|OPTIONS这4类方法会被放行,其它Method的http请求,都要验证_csrf的token是否正确,而通常post方式调用rest接口服务时,又没有_csrf的token,所以会导致我们的rest接口调用失败,我们需要自定义一个类对该类型接口进行放行。来看下我们自定义的过滤器:
源码2:csrfSecurityRequestMatcher类
public class CsrfSecurityRequestMatcher implements RequestMatcher {
    private Pattern allowedMethods = Pattern.compile("^(GET|HEAD|TRACE|OPTIONS)$");
    private RegexRequestMatcher unprotectedMatcher = new RegexRequestMatcher("^/rest/.*", null);

    @Override
    public boolean matches(HttpServletRequest request) {
        if(allowedMethods.matcher(request.getMethod()).matches()){
            return false;
        }

        return !unprotectedMatcher.matches(request);
    }
}

说明:一般我们定义的rest接口服务,都带上 /rest/ ,所以如果你的项目中不是使用的这种,或者项目中没有rest服务,这个类完全可以省略的。

5.post请求配置

一般我们的项目中都有一个通用的jsp文件,就是每个页面都会引用的,所以我们可以在通用文件中做如下配置:



$.ajaxSetup的意思就是给我们所有的请求都加上这个header和token,或者放到form表单中。注意, _csrf这个要与spring security的配置文件中的配置相匹配,默认为_csrf

源码解析

我们知道,既然配置了csrf,所有的http请求都会被CsrfFilter拦截到,所以看下CsrfFilter的源码就对原理一目了然了。这里我们只看具体过滤的方法即可:
源码3:CsrfFilter的doFilterInternal方法
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        request.setAttribute(HttpServletResponse.class.getName(), response);
        CsrfToken csrfToken = this.tokenRepository.loadToken(request);
        boolean missingToken = csrfToken == null;
        if(missingToken) {//如果token为空,说明第一次访问,生成一个token对象
            csrfToken = this.tokenRepository.generateToken(request);
            this.tokenRepository.saveToken(csrfToken, request, response);
        }

        request.setAttribute(CsrfToken.class.getName(), csrfToken);
		//把token对象放到request中,注意这里key是csrfToken.getParameterName()= _csrf,所以我们页面上才那么写死。
        request.setAttribute(csrfToken.getParameterName(), csrfToken);
		
		//这个macher就是我们在Spring配置文件中自定义的过滤器,也就是GET,HEAD, TRACE, OPTIONS和我们的rest都不处理
        if(!this.requireCsrfProtectionMatcher.matches(request)) {
            filterChain.doFilter(request, response);
        } else {
            String actualToken = request.getHeader(csrfToken.getHeaderName());
            if(actualToken == null) {
                actualToken = request.getParameter(csrfToken.getParameterName());
            }

            if(!csrfToken.getToken().equals(actualToken)) {
                if(this.logger.isDebugEnabled()) {
                    this.logger.debug("Invalid CSRF token found for " + UrlUtils.buildFullRequestUrl(request));
                }

                if(missingToken) {
                    this.accessDeniedHandler.handle(request, response, new MissingCsrfTokenException(actualToken));
                } else {
                    this.accessDeniedHandler.handle(request, response, new InvalidCsrfTokenException(csrfToken, actualToken));
                }

            } else {
                filterChain.doFilter(request, response);
            }
        }
    }
从源码中可以看到,通过我们自定义的过滤器以外的post请求都需要进行token验证。

本来呢,是想截图弄个案例上去的,然后通过断点看看页面和后台的传值情况....不过,我这里没法上传图片。好吧,就总结这么多吧!如果有写的不对的或者有其他问题可以留言交流。


你可能感兴趣的:(JAVA框架之路,------【框架基础】)