欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

详解利用spring-security解决CSRF问题

程序员文章站 2023-12-05 22:16:40
csrf介绍 csrf(cross-site request forgery),中文名称:跨站请求伪造,也被称为:one click attack/session rid...

csrf介绍

csrf(cross-site request forgery),中文名称:跨站请求伪造,也被称为:one click attack/session riding,缩写为:csrf/xsrf。

具体scrf的介绍和攻击方式请参看百度百科的介绍和一位大牛的分析:
csrf百度百科
浅谈csrf攻击方式

配置步骤

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, response, new invalidcsrftokenexception(csrftoken, actualtoken)); 
        } 
 
      } else { 
        filterchain.dofilter(request, response); 
      } 
    } 
  } 

从源码中可以看到,通过我们自定义的过滤器以外的post请求都需要进行token验证。

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

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。