Spring Security 簡明架構
Spring Security 主要涉及2大核心功能:
Authentication and Access Control
- authentication - who are you? (你是誰)- 認證
- access control or authorization - what are you allowed to do? (你能幹什麼) - 授權
1 Authentication 認證
認證的核心接口是 認證管理器AuthenticationManager
, 它只有一個方法:
public interface AuthenticationManager {
Authentication authenticate(Authentication authentication)
throws AuthenticationException;
}
一個認證管理器可以通過 authenticate()
處理三種情況,這三種結果涵蓋了所有出現的認證情況:
- 認證成功,返回一個認證成功的對象
Authentication
(屬性authenticated=true,在未認證之前authenticated=false),即代表輸入是一個合法的身份 - 拋出一個認證異常
AuthenticationException
, 代表能夠確定的輸入非法的各種情況,該異常後續會被認證流程的其他類處理轉譯爲用戶可讀的結果,所有我們無需處理 - 如果認證管理器也無法判斷的話就返回
null
, 因爲可能還有其它的認證器來處理
AuthenticationManager
最常用的實現類是ProviderManager
, 它維護了一個鏈表的``AuthenticationProvider的實例,
AuthenticationProvider有點類似於
AuthenticationManager認證管理器,但是它添加了一個
supports()`方法,用於判斷當前輸入的被認證對象是否能夠被這個認證器所處理:
public interface AuthenticationProvider {
Authentication authenticate(Authentication authentication)
throws AuthenticationException;
boolean supports(Class<?> authentication);
}
一個認證提供器 ProviderManager
能夠支持多種不同的認證方法,如果不支持所認證的對象那就會跳過它。一個ProviderManager
有一個可選的 parent
(雙親認證提供器),如果所有的認證對象它都不支持的話,parent
就會用來做最後的認證。如果parent ProviderManager
不可用話就會拋出一個 認證異常AuthenticationException
。
通常在一個應用程序中,我們會將受保護的資源按權限進行分組,比如 Web 系統中,只允許/api/**
的請求進入,/resources/**
訪問靜態資源,在這種情況下,每組資源都對應一個 AuthenticationManager
, 所有的認證器都共享一個全局的認證器作爲所有認證器的頂級雙親。
Spring Boot 提供了一個默認配置的全局認證管理器(Global AuthenticationManager)通過@Autowired
自動注入``AuthenticationManagerBuilder可以對其進行配置,一般情況已經足夠使用。
@Configuration
public class ApplicationSecurity extends WebSecurityConfigurerAdapter {
... // 其他配置
@Autowired
public void initialize(AuthenticationManagerBuilder builder, DataSource dataSource) {
builder.jdbcAuthentication().dataSource(dataSource).withUser("dave")
.password("secret").roles("USER");
}
}
以上是通過自動注入一個AuthenticationManagerBuilder
來對這個全局的認證器進行配置,通常一個簡單的web 應用這麼配置是可以的,但是當我們有許多認證規則的時候,配置全局認證器並不是一個很好的解決方案,這時我們就可以單獨去配置每一個AuthenticationManager
:
@Configuration
public class ApplicationSecurity extends WebSecurityConfigurerAdapter {
@Autowired
DataSource dataSource;
... // 其他配置
@Override
public void configure(AuthenticationManagerBuilder builder) {
builder.jdbcAuthentication().dataSource(dataSource).withUser("dave")
.password("secret").roles("USER");
}
}
通過重寫WebSecurityConfigurerAdapter
的configure(AuthenticationManagerBuilder builder)
來配置一個新的認證器,此認證器是上面全局認證器的下級,它的作用域僅限於當前這個配置類所對應的規則,或者也可以稱爲Local AuthenticationManager
。
2 Authorization 授權
當認證成功之後,接下來的一步就是授權了,授權最核心的類是AccessDecisionManager
。Spring Security 提供了三種實現類,這三種實現分別都維護了各自的一個鏈表的AccessDecisionVoter
,非常類似於 ``ProviderManager來代理一個鏈表的
AuthenticationProvider`一樣。
這裏的AccessDecisionVoter
我們可以簡單稱它爲“投票器”,就像投票人一樣,如果覺得你有授權就會投票贊成一樣,很形象。
AccessDecisionManager
我們可以通過字面意思將它理解爲訪問決策管理器,它的作用就是來決定訪問者是否真的有權限訪問對應資源,它的三種實現機制分別是:
- AffirmativeBased:只要有一個投票器通過則代表授權成功 (
默認配置
) - ConsensusBased:少數服從多數原則,即多數AccessDecisionVoter通過則代表授權成功
- UnanimousBased:全部贊同才代表授權成功
我們可以來看一下這些投票者的接口AccessDecisionVoter
:
int ACCESS_GRANTED = 1; //授權成功
int ACCESS_ABSTAIN = 0; //棄權
int ACCESS_DENIED = -1; //否決
boolean supports(ConfigAttribute attribute);
boolean supports(Class<?> clazz);
int vote(Authentication authentication, S object,
Collection<ConfigAttribute> attributes);
vote()
方法即代表投票,Authentication 代表認證通過的憑證,存儲着一些身份信息, S object 這個 object 是一個泛型的參數,代表着一切要被訪問的資源(一個靜態資源或一個方法等等),ConfigAttribute
指代訪問這些資源的屬性,該接口只有一個方法getAttribute()
返回一個字符串,這個字符串以某種形式代表了可以訪問一個資源的規則。最常見的是代表角色權限的字符串,比如在 Spring Security 中默認的以 ROLE_
爲前綴的角色,這些屬性通常以特殊的格式存在,或者是一個SpEL
表達式,如isFullyAuthenticated() && hasRole('FOO')
。如果要自定義能夠解析的 SpEL 的話,可以實現 SecurityExpressionRoot
或SecurityExpressionHandler
Web Security - Web 安全
我們都知道在一個請求到達 Servlet 真正執行之前,會經過一系列的過濾器,然後纔會執行 service()
方法,Spring Security 就是主要以 Filter
(過濾器) 的方式在 Web 應用程序中起作用。
Spring Security 以一個單獨的 Filter 註冊在 Web 容器內部,這個特殊的 Filter 就是FilterChainProxy
, 它自身維護了一個內部的過濾器鏈,用來做權限相關的處理,也就是上圖中的 Spring Security Filters。在一個 Spring Boot 項目中,FilterChainProxy
是作爲一個@Bean
默認加載在ApplicationContext
中的,其在 Filter 中的執行順序由SecurityProperties.DEFAULT_FILTER_ORDER
決定。
對於 Spring Security 來說,可以存在多個過濾器鏈,每一個過濾器鏈匹配一個或一組請求,如下圖/foo/**
,/bar/**
,/**
分別對應一組 Filter Chain
配置自定義的過濾器鏈也比較簡單,通常設置一個WebSecurityConfigurerAdapter
作爲配置類即可,如下:
@Configuration
@Order(SecurityProperties.BASIC_AUTH_ORDER - 10)
public class ApplicationConfigurerAdapter extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/foo/**")
...
}
}
以上配置類將會使 Spring Security 創建一個新的 Filter Chain 並且它將會在BASIC_AUTH_ORDER
之前執行。
許多應用程序對於不同的資源由完全不一樣的訪問權限設置,比如一個網站對於瀏覽器 UI 的訪問可能是基於 cookie 認證的,認證成功後會進行頁面跳轉,而對於 API 的訪問則是基於 token 的訪問規則,認證失敗則返回一個 401 的權限錯誤。對於每種不同的訪問規則,可以配置單獨的``WebSecurityConfigurerAdapter處理。
一個網站對於瀏覽器 UI 的訪問可能是基於 cookie 認證的,認證成功後會進行頁面跳轉,而對於 API 的訪問則是基於 token 的訪問規則,認證失敗則返回一個 401 的權限錯誤。對於每種不同的訪問規則,可以配置單獨的``WebSecurityConfigurerAdapter處理。
一個普通的 Spring Boot 應用內部默認配置了多個過濾器鏈。在/**
的默認過濾器鏈中包含了認證邏輯,授權規則,異常處理,Session 處理,Http Header 設置等等 11 個默認的 Filter,一般情況下用戶無需關心。
---- 部分翻譯自 Spring Security
參考資料:
https://spring.io/guides/topicals/spring-security-architecture/