保護 Spring Boot 應用程式的第一步是将 spring-boot-starter-security 依賴項添加到建構中。在項目的 pom.xml 檔案中,添加以下依賴項:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
上面的依賴項是保護應用程式所需的唯一東西。當應用程式啟動時,自動配置将檢測類路徑中的 Spring Security包,并設定一些基本的安全性配置。
一、輸出預設過濾器鍊
Spring Security 本質就是基于過濾器鍊實作的,每一個接口請求都會按順序經過這些過濾器的“過濾”,每個過濾器承擔的各自的職責,組合起來共同完成認證和鑒權。
Spring Security會自動建立一個名為 springSecurityFilterChain 的過濾器鍊,并注入到Spring 容器中,這個過濾器将負責所有的安全管理,包括使用者的認證、授權、重定向到登入頁面等。
通過 FilterChainProxy 來統一管理 Spring Security Filter,FilterChainProxy 本身則通過 Spring 提供的 DelegatingFilterProxy 代理過濾器嵌入到 Web Filter 之中。
可以簡單修改一下Spring Boot啟動類,如下所示:
@SpringBootApplication
public class FirstApp
{
public static void main( String[] args )
{
ConfigurableApplicationContext context = SpringApplication.run(FirstApp.class, args);
FilterChainProxy proxy = (FilterChainProxy) context.getBean("springSecurityFilterChain");
if (proxy != null) {
List<Filter> filters = proxy.getFilterChains().get(0).getFilters();
filters.forEach(x-> System.out.println(x));
}
System.out.println("hello spring security");
}
}
就可以列印輸出Spring Security中的預設過濾器鍊,如下所示:
org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@67dba613
org.springframework.security.web.context.SecurityContextPersistenceFilter@79a1728c
org.springframework.security.web.header.HeaderWriterFilter@71391b3f
org.springframework.security.web.csrf.CsrfFilter@5d235104
org.springframework.security.web.authentication.logout.LogoutFilter@44c5a16f
org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter@29fc1a2b
org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter@66596a88
org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter@57540fd0
org.springframework.security.web.authentication.www.BasicAuthenticationFilter@677b8e13
org.springframework.security.web.savedrequest.RequestCacheAwareFilter@41f35f7c
org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@3013909b
org.springframework.security.web.authentication.AnonymousAuthenticationFilter@5cf8edcf
org.springframework.security.web.session.SessionManagementFilter@12db3386
org.springframework.security.web.access.ExceptionTranslationFilter@6fca2a8f
org.springframework.security.web.access.intercept.FilterSecurityInterceptor@3a4ba480
上述預設過濾器鍊的過濾器,和 WebSecurityConfigurerAdapter 安全配置類的兩段代碼有關系。
1. configure(HttpSecurity http) 方法
在configure() 方法中,預設開啟了 formLogin 表單認證和 httpBasic() 基礎認證。
protected void configure(HttpSecurity http) throws Exception {
logger.debug("Using default configure(HttpSecurity). If subclassed this will potentially override subclass configure(HttpSecurity).");
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin().and()
.httpBasic();
}
2. getHttp() 方法
在 getHttp() 方法中,預設開啟了 csrf、exceptionHandling、headers .... 等等好的過濾器,具體檢視下面的代碼。
protected final HttpSecurity getHttp() throws Exception {
if (http != null) {
return http;
}
AuthenticationEventPublisher eventPublisher = getAuthenticationEventPublisher();
localConfigureAuthenticationBldr.authenticationEventPublisher(eventPublisher);
AuthenticationManager authenticationManager = authenticationManager();
authenticationBuilder.parentAuthenticationManager(authenticationManager);
Map<Class<?>, Object> sharedObjects = createSharedObjects();
http = new HttpSecurity(objectPostProcessor, authenticationBuilder,
sharedObjects);
if (!disableDefaults) {
// @formatter:off
http
.csrf().and()
.addFilter(new WebAsyncManagerIntegrationFilter())
.exceptionHandling().and()
.headers().and()
.sessionManagement().and()
.securityContext().and()
.requestCache().and()
.anonymous().and()
.servletApi().and()
.apply(new DefaultLoginPageConfigurer<>()).and()
.logout();
// @formatter:on
ClassLoader classLoader = this.context.getClassLoader();
List<AbstractHttpConfigurer> defaultHttpConfigurers =
SpringFactoriesLoader.loadFactories(AbstractHttpConfigurer.class, classLoader);
for (AbstractHttpConfigurer configurer : defaultHttpConfigurers) {
http.apply(configurer);
}
}
configure(http);
return http;
}
上述 getHttp() 方法是在下面的 init() 方法中被調用的,init() 方法的代碼如下所示:
public void init(final WebSecurity web) throws Exception {
final HttpSecurity http = getHttp();
web.addSecurityFilterChainBuilder(http).postBuildAction(() -> {
FilterSecurityInterceptor securityInterceptor = http
.getSharedObject(FilterSecurityInterceptor.class);
web.securityInterceptor(securityInterceptor);
});
}
同時,在該方法中也添加了 FilterSecurityInterceptor 過濾器。
現在,應該就了解了前面代碼中預設輸出的過濾器中的過濾器清單了。
二、清理過濾器鍊中全部過濾器
那麼有沒有将過濾器鍊中的所有過濾器全部清理掉呢?
可以 ... 隻需要建立一個 SecurityConfig 安全配置類,繼承 WebSecurityConfigurerAdapter 類。然後重寫其中的如下方法:
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
protected SecurityConfig() {
super(true);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
}
}
這樣,Spring Security 中的預設過濾器鍊中的過濾器就全部去除了。
三、DefaultSecurityFilterChain
SecurityFilterChain 就是我們平時所說的 Spring Security 中的過濾器鍊。
它裡邊定義了兩個方法,一個是 matches 方法用來比對請求,另外一個 getFilters 方法傳回一個 List 集合,集合中放着 Filter 對象。
當一個請求到來時,用 matches 方法去比較請求是否和目前鍊吻合,如果吻合,就傳回 getFilters 方法中的過濾器,那麼目前請求會逐個經過 List 集合中的過濾器。
SecurityFilterChain 接口隻有一個實作類,那就是 DefaultSecurityFilterChain,如圖所示:
DefaultSecurityFilterChain實作了SecurityFilterChain接口。
DefaultSecurityFilterChain 其實就相當于是 Spring Security 中的過濾器鍊,一個 DefaultSecurityFilterChain 代表一個過濾器鍊,如果系統中存在多個過濾器鍊,則會存在多個 DefaultSecurityFilterChain 對象。
FilterChainProxy 中可以存在多個過濾器鍊(DefaultSecurityFilterChain),如圖所示:
前面我們有一段輸出預設過濾器的代碼,就是擷取Spring Security 中的第一個 DefaultSecurityFilterChain 對象:
List<Filter> filters = proxy.getFilterChains().get(0).getFilters();
可以看到,當請求到達 FilterChainProxy 之後,FilterChainProxy 會根據請求的路徑,将請求轉發到不同的 Spring Security 過濾器鍊上面去,不同的 Spring Security Filters(過濾器鍊) 對應了不同的過濾器,也就是不同的請求将經過不同的過濾器。