详解利用spring-security解决CSRF问题
CSRF介绍 CSRF(Cross-site request forgery),中文名称:跨站请求伪造,也被称为:one click attack/session riding,缩写为:CSRF/XSRF。 具体SCRF的介绍和攻击方式请参看百度百科的介绍和一位大牛的分析: 配置步骤 1.依赖jar包 <properties> <spring.security.version>4.2.2.RELEASE</spring.security.version> </properties> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-core</artifactId> <version>${spring.security.version}</version> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-web</artifactId> <version>${spring.security.version}</version> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-config</artifactId> <version>${spring.security.version}</version> </dependency> 2.web.xml配置 <filter> <filter-name>springSecurityFilterChain</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> </filter> <filter-mapping> <filter-name>springSecurityFilterChain</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> 3.Spring配置文件配置 <bean id="csrfSecurityRequestMatcher" class="com.xxx.CsrfSecurityRequestMatcher"></bean> <security:http auto-config="true" use-expressions="true"> <security:headers> <security:frame-options disabled="true"/> </security:headers> <security:csrf request-matcher-ref="csrfSecurityRequestMatcher" /> </security:http> 4.自定义RequestMatcher的实现类CsrfSecurityRequestMatcher 这个类被用来自定义哪些请求是不需要进行拦截过滤的。如果配置csrf,所有http请求都被会CsrfFilter拦截,而CsrfFilter中有一个私有类DefaultRequiresCsrfMatcher。 源码1:DefaultRequiresCsrfMatcher类 private static final class DefaultRequiresCsrfMatcher implements RequestMatcher { private final HashSet<String> 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文件,就是每个页面都会引用的,所以我们可以在通用文件中做如下配置: <meta name="_csrf" content="${_csrf.token}"/> <meta name="_csrf_header" content="${_csrf.headerName}"/> <script> var token = $("meta[name='_csrf']").attr("content"); var header = $("meta[name='_csrf_header']").attr("content"); $.ajaxSetup({ beforeSend: function (xhr) { if(header && token ){ xhr.setRequestHeader(header,token); } }} ); </script> $.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,new InvalidCsrfTokenException(csrfToken,actualToken)); } } else { filterChain.doFilter(request,response); } } } 从源码中可以看到,通过我们自定义的过滤器以外的post请求都需要进行token验证。 本来呢,是想截图弄个案例上去的,然后通过断点看看页面和后台的传值情况....不过,我这里没法上传图片抓狂。好吧,就总结这么多吧!如果有写的不对的或者有其他问题可以留言交流。 以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持编程小技巧。 您可能感兴趣的文章:
(编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |