Spring Security 配置中的 and 到底該怎麼理解?

我們先來看一個簡單的配置:

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests()
            .anyRequest().authenticated()
            .and()
            .formLogin()
            .loginProcessingUrl("/doLogin")
            .permitAll()
            .and()
            .logout()
            .logoutUrl("/logout")
            .permitAll()
            .and()
            .csrf().disable();
}

這樣的配置在 Spring Security 中很常見,通過 and 方法,可以將所有的配置連接在一起,一條線下來,所有的東西都配置好了。

但是有小夥伴對這裏的 and 表示很迷,不知道什麼時候 and 方法該出場,什麼時候 and 不該出場!

所以今天松哥就花點時間來和大家聊一下這裏的 and 方法,希望大家看完完整後,能夠明白 and 到底怎麼玩!

本文是 Spring Security 系列第 33 篇,閱讀前面文章有助於更好的理解本文:

  1. 挖一個大坑,Spring Security 開搞!
  2. 松哥手把手帶你入門 Spring Security,別再問密碼怎麼解密了
  3. 手把手教你定製 Spring Security 中的表單登錄
  4. Spring Security 做前後端分離,咱就別做頁面跳轉了!統統 JSON 交互
  5. Spring Security 中的授權操作原來這麼簡單
  6. Spring Security 如何將用戶數據存入數據庫?
  7. Spring Security+Spring Data Jpa 強強聯手,安全管理只有更簡單!
  8. Spring Boot + Spring Security 實現自動登錄功能
  9. Spring Boot 自動登錄,安全風險要怎麼控制?
  10. 在微服務項目中,Spring Security 比 Shiro 強在哪?
  11. SpringSecurity 自定義認證邏輯的兩種方式(高級玩法)
  12. Spring Security 中如何快速查看登錄用戶 IP 地址等信息?
  13. Spring Security 自動踢掉前一個登錄用戶,一個配置搞定!
  14. Spring Boot + Vue 前後端分離項目,如何踢掉已登錄用戶?
  15. Spring Security 自帶防火牆!你都不知道自己的系統有多安全!
  16. 什麼是會話固定攻擊?Spring Boot 中要如何防禦會話固定攻擊?
  17. 集羣化部署,Spring Security 要如何處理 session 共享?
  18. 松哥手把手教你在 SpringBoot 中防禦 CSRF 攻擊!so easy!
  19. 要學就學透徹!Spring Security 中 CSRF 防禦源碼解析
  20. Spring Boot 中密碼加密的兩種姿勢!
  21. Spring Security 要怎麼學?爲什麼一定要成體系的學習?
  22. Spring Security 兩種資源放行策略,千萬別用錯了!
  23. 松哥手把手教你入門 Spring Boot + CAS 單點登錄
  24. Spring Boot 實現單點登錄的第三種方案!
  25. Spring Boot+CAS 單點登錄,如何對接數據庫?
  26. Spring Boot+CAS 默認登錄頁面太醜了,怎麼辦?
  27. 用 Swagger 測試接口,怎麼在請求頭中攜帶 Token?
  28. Spring Boot 中三種跨域場景總結
  29. Spring Boot 中如何實現 HTTP 認證?
  30. Spring Security 中的四種權限控制方式
  31. Spring Security 多種加密方案共存,老破舊系統整合利器!
  32. 神奇!自己 new 出來的對象一樣也可以被 Spring 容器管理!

1.原始配置

在 Spring Boot 出現之前,我們使用 Spring Security ,都是通過 XML 文件來配置 Spring Security 的,即使現在大家在網上搜索 Spring Security 的文章,還是能夠找到很多 XML 配置的。

但是小夥伴們明白,無論是 XML 配置還是 Java 配置,只是在用不同的方式描述同一件事情,從這裏角度來看,我們現在所使用的 Java 配置,和以前使用的 XML 配置,應該有某種異曲同工之妙。

可能有小夥伴沒見過 XML 配置的 Spring Security,我在這裏給大家簡單舉幾個例子:

<http>
    <intercept-url pattern="/login" access="permitAll" />
    <form-login login-page="/login" />
    <http-basic />
</http>

這段 XML 大家稍微瞅一眼大概就能明白其中的含義:

  1. intercept-url 相當於配置攔截規則
  2. form-login 是配置表單登錄
  3. http-basic 是配置 HttpBasic 認證

如果我們使用了 Java 配置,這些 XML 配置都有對應的寫法,例如 .anyRequest().authenticated() 就是配置攔截規則的,.formLogin() 是配置表單登錄細節的。

僅僅從語義層面來理解,and 有點類似於 XML 中的結束標籤,每當 and 出現,當前的配置項就結束了,可以開啓下一個配置了。

那麼從代碼層面上,這個要如何理解呢?

2.代碼層面的理解

小夥伴們知道,Spring Security 中的功能是由一系列的過濾器來實現的,默認的過濾器一共有 15 個,這 15 個過濾器松哥以後會和大家挨個介紹。

每一個過濾器都有一個對應的 configurer 來對其進行配置,例如我們常見的 UsernamePasswordAuthenticationFilter 過濾器就是通過 AbstractAuthenticationFilterConfigurer 來進行配置的。

這些 configure 都有一個共同的父類,那就是 SecurityConfigurer,給大家大致看一下 SecurityConfigurer 的繼承關係圖:

可以看到,它的實現類還是蠻多的。

SecurityConfigurer 的源碼很簡單:

public interface SecurityConfigurer<O, B extends SecurityBuilder<O>> {
	void init(B builder) throws Exception;
	void configure(B builder) throws Exception;
}

就兩個方法,第一個 init 用來做初始化操作,第二個 configure 用來做具體的配置。

在 Spring Security 框架初始化的時候,會把所有的這些 xxxConfigurer 收集起來,然後再統一調用每一個 xxxConfigurer 裏邊的 init 和 configure 方法(松哥在以後的文章中會和大家詳細討論這個過程),調用完成後,Spring Security 默認的過濾器鏈就形成了。

這就是我們所說的 xxxConfigurer 的作用!

在文章一開始,松哥列出來的示例代碼中,HttpSecurity 中其實就是在配置各種各樣的 xxxConfigurer。

SecurityConfigurer 有一個重要的實現類就是 SecurityConfigurerAdapter,默認的 15 個過濾器的 Configurer 類都是繼承自它!而在 SecurityConfigurerAdapter 中就多出來一個方法:

public abstract class SecurityConfigurerAdapter<O, B extends SecurityBuilder<O>>
		implements SecurityConfigurer<O, B> {

	public void init(B builder) throws Exception {
	}

	public void configure(B builder) throws Exception {
	}
	public B and() {
		return getBuilder();
	}

}

沒錯,就是大家所熟知的 and 方法。and 方法的返回值是一個 SecurityBuilder 的子類,其實就是 HttpSecurity,也就是 and 方法總是讓我們回到 HttpSecurity,從而開啓新一輪的 xxxConfigurer 配置。

我們再來瞅一眼 HttpSecurity 中到底都有啥方法(方法比較多,我這裏僅列舉一部分):

可以看到,每一個類型的配置,都有一個對應的返回 Configure 的方法,例如 OpenIDLoginConfigurer、HeadersConfigurer、CorsConfigurer 等等,大家注意,每一個 configure 方法都有一個 HttpSecurity 作爲泛型,這實際上就指定了 and 方法的返回類型。

我再舉個例子,大家可能更清楚一些,以 HttpSecurity 中 RememberME 的配置爲例,有兩個方法:

  • RememberMeConfigurer rememberMe()
  • HttpSecurity rememberMe(Customizer<RememberMeConfigurer> rememberMeCustomizer)
  1. 第一個 rememberMe 方法沒有參數,但是返回值是一個 RememberMeConfigurer,我們可以在這個 RememberMeConfigurer 上繼續配置 RememberME 相關的其他屬性,配置完成後,通過 and 方法重新回到 HttpSecurity 對象,松哥前面文章基本上都是採用這種方式配置的,這裏我就不重複舉例子了。
  2. 第二個 rememberMe 方法有參數,參數是一個 Customizer ,但是帶着一個 RememberMeConfigurer 泛型。其實 Customizer 就是一個接口,我們可以通過匿名內部類的方式來實現該接口,這個接口中就一個實例方法,而且該方法的參數還是你傳入的泛型,即 RememberMeConfigurer,其實也就是我們換了個地方去配置 RememberMeConfigurer 了,配置完成後,這個方法會直接返回 HttpSecurity,此時就不再需要 and 方法了。配置示例如下(注意配置完成後不需要 and 方法就能繼續後面的配置了):
@Override
protected void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests()
            .antMatchers("/admin/**").hasRole("admin")
            .antMatchers("/user/**").hasRole("user")
            .anyRequest().authenticated()
            .and()
            .formLogin()
            .permitAll()
            .and()
            .rememberMe(new Customizer<RememberMeConfigurer<HttpSecurity>>() {
                @Override
                public void customize(RememberMeConfigurer<HttpSecurity> httpSecurityRememberMeConfigurer) {
                    httpSecurityRememberMeConfigurer.key("123");
                }
            })
            .csrf().disable();
}

這就是我們在 configure(HttpSecurity http) 方法中的配置過程。

3.小結

通過前面的講解,不知道小夥伴們有沒有看懂呢?我再給大家總結下。

Spring Security 的功能主要是通過各種各樣的過濾器來實現的,各種各樣的過濾器都由對應的 xxxConfigurer 來進行配置,我們在 configure(HttpSecurity http) 中所做的配置其實就是在配置 xxxConfigurer,也是在間接的配置過濾器,每一個 and 方法會將我們帶回到 HttpSecurity 實例中,從而開啓新一輪的配置。

大致就是這樣!文章案例下載地址:https://github.com/lenve/spring-security-samples

小夥伴們如果覺得有收穫,記得點個在看鼓勵下松哥哦~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章