Spring Security Filter 學習筆記

hookind發表於2021-12-24

過濾器可以簡單理解成用於攔截請求,並執行相應邏輯的程式碼。

在Spring Security架構中實現過濾器

在SpringSecurity中,可以通過實現 javax.servlet 包中的 Filter介面構造過濾器。

我們通過實現Filter 介面的doFilter() 方法,執行相關邏輯。該方法包含三個引數:

  1. ServletRequest:表示http請求,可用它獲得請求相關資訊。
  2. ServletResponse:表示http響應,可向它新增相關資訊,最後傳回客戶端。
  3. FilterChain:表示過濾鏈,用於把請求和響應轉發到過濾鏈上的下一個過濾器。

Spring Security為我們提供了一些過濾器實現,例如:

  • BasicAuthenticationFilter:用於http認證。
  • CsrfFilter:用於跨請求保護。
  • CorsFilter:負責跨域資源共享 (CORS) 授權規則。

多個過濾器集合在一起形成一條過濾鏈,它們之間有一定順序。你可以通過存在於過濾鏈上的一個過濾器,在它的相對位置新增一個新的過濾器。

在過濾鏈上的一個過濾器前面,新增一個新的過濾器

可以通過下面的方法在某過濾器前面新增一個新的過濾器。

@Configuration
public class ProjectConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.addFilterBefore(new CustomFilter(), BasicAuthenticationFilter.class);
    }

}

CustomFilter是你自定義實現的過濾器類,BasicAuthenticationFilter是認證過濾器的預設型別。

在過濾鏈上的一個過濾器後面,新增一個新的過濾器

下面的程式碼是在某過濾器後面新增一個新的過濾器。

@Configuration
public class ProjectConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.addFilterAfter(new CustomFilter(), BasicAuthenticationFilter.class);
    }

}

在過濾鏈上的一個過濾器位置,新增一個新的過濾器

下面程式碼是在一個過濾器的位置上,新增一個新的過濾器。

@Configuration
public class ProjectConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http..addFilterAt(new CustomFilter(), BasicAuthenticationFilter.class);
    }

}

當在一個過濾器的位置上,加入一個新的過濾器,Spring Security不會假設該位置上只有一個過濾器, 它也不會保證該位置上過濾器的順序。

Spring Security 提供的過濾器實現

Spring Security 提供了一些實現Filter藉口的抽象類,並在裡面加入一些功能。你可以通過繼承這些類來構建過濾器類。例如OncePerRequestFilter等。由於Spring Security不能保證一個過濾器對於同一個請求不會被呼叫多次,我們可以是過濾器繼承OncePerRequestFilter來保證。

演示程式碼

在過濾器BasicAuthenticationFilter的前面、當前位置、後面各新增一個新的過濾器。

建立一個SpringBoot專案,它的依賴如下:

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

建立三個過濾器,分別為BeforeFilter、CurrentFilter、AfterFilter。

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

public class BeforeFilter implements Filter {

  @Override
  public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
      throws IOException, ServletException {
    System.out.println("BeforeFilter: " + "在其前面插入的過濾器");
    chain.doFilter(request, response);
  }

}
import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

public class CurrentFilter implements Filter {

  @Override
  public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
      throws IOException, ServletException {
    System.out.println("CurrentFilter: " + "在其當前位置插入的過濾器");
    chain.doFilter(request, response);
  }

}


import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

public class AfterFilter implements Filter {

  @Override
  public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
      throws IOException, ServletException {
    System.out.println("AfterFilter: " + "在其後面插入的過濾器");
    chain.doFilter(request, response);
  }

}

建立一個配置類ProjectConfig

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;

import hookind.security009.filter.AfterFilter;
import hookind.security009.filter.BeforeFilter;
import hookind.security009.filter.CurrentFilter;

@Configuration
public class ProjectConfig extends WebSecurityConfigurerAdapter {

  @Override
  protected void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests().anyRequest().permitAll();
    // 在前面加入一個過濾器
    http.addFilterBefore(new BeforeFilter(), BasicAuthenticationFilter.class);
    // 在當前位置加入一個過濾器
    http.addFilterAt(new CurrentFilter(), BasicAuthenticationFilter.class);
    // 在其後面加入一個過濾器
    http.addFilterAfter(new AfterFilter(), BasicAuthenticationFilter.class);
  }
}

建立一個Controller類

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {
  
  @GetMapping("/hello")
  public String hello(){
    return "Hello";
  }
}

啟動程式後,在瀏覽器輸入網址 http://localhost:8080/hello,我的控制檯輸出如下:

BeforeFilter: 在其前面插入的過濾器
CurrentFilter: 在其當前位置插入的過濾器
AfterFilter: 在其後面插入的過濾器
BeforeFilter: 在其前面插入的過濾器
CurrentFilter: 在其當前位置插入的過濾器
AfterFilter: 在其後面插入的過濾器
BeforeFilter: 在其前面插入的過濾器
CurrentFilter: 在其當前位置插入的過濾器
AfterFilter: 在其後面插入的過濾器

由上可知,過濾器是按BeforeFilter、CurrentFilter、AfterFilter順序呼叫的。也表示,對於同一個請求,Spring Security可能會呼叫同一個過濾器多次。

相關文章