鬆哥原創的 Spring Boot 視頻教程已經殺青,感興趣的小夥伴戳這裏-->Spring Boot+Vue+微人事視頻教程java
我們繼續來擼 Spring Security 源碼。web
前面和你們分享了 SecurityBuilder 以及它的一個重要實現 HttpSecurity,在 SecurityBuilder 的實現類裏邊,還有一個重要的分支,那就是 AuthenticationManagerBuilder,AuthenticationManagerBuilder 看名字就知道是用來構建 AuthenticationManager 的,因此今天咱們就來看一看 AuthenticationManager 究竟是怎麼構建的。緩存
1.初步理解
在 Spring Security 中,用來處理身份認證的類是 AuthenticationManager,咱們也稱之爲認證管理器。微信
AuthenticationManager 中規範了 Spring Security 的過濾器要如何執行身份認證,並在身份認證成功後返回一個通過認證的 Authentication 對象。AuthenticationManager 是一個接口,咱們能夠自定義它的實現,可是一般咱們使用更多的是系統提供的 ProviderManager。架構
1.1 ProviderManager
ProviderManager 是的最經常使用的 AuthenticationManager 實現類。app
ProviderManager 管理了一個 AuthenticationProvider 列表,每一個 AuthenticationProvider 都是一個認證器,不一樣的 AuthenticationProvider 用來處理不一樣的 Authentication 對象的認證。一次完整的身份認證流程可能會通過多個 AuthenticationProvider。編輯器
ProviderManager 至關於代理了多個 AuthenticationProvider,他們的關係以下圖:ide
![](http://static.javashuo.com/static/loading.gif)
1.2 AuthenticationProvider
AuthenticationProvider 定義了 Spring Security 中的驗證邏輯,咱們來看下 AuthenticationProvider 的定義:工具
public interface AuthenticationProvider {
Authentication authenticate(Authentication authentication)
throws AuthenticationException;
boolean supports(Class<?> authentication);
}
能夠看到,AuthenticationProvider 中就兩個方法:源碼分析
-
authenticate 方法用來作驗證,就是驗證用戶身份。 -
supports 則用來判斷當前的 AuthenticationProvider 是否支持對應的 Authentication。
在一次完整的認證中,可能包含多個 AuthenticationProvider,而這多個 AuthenticationProvider 則由 ProviderManager 進行統一管理,具體能夠參考鬆哥以前的文章:鬆哥手把手帶你捋一遍 Spring Security 登陸流程。
最經常使用的 AuthenticationProvider 實現類是 DaoAuthenticationProvider。
1.3 Parent
每個 ProviderManager 管理多個 AuthenticationProvider,同時每個 ProviderManager 均可以配置一個 parent,若是當前的 ProviderManager 中認證失敗了,還能夠去它的 parent 中繼續執行認證,所謂的 parent 實例,通常也是 ProviderManager,也就是 ProviderManager 的 parent 仍是 ProviderManager。能夠參考以下架構圖:
![](http://static.javashuo.com/static/loading.gif)
從上面的分析中你們能夠看出,AuthenticationManager 的初始化會分爲兩塊,一個全局的 AuthenticationManager,也就是 parent,另外一個則是局部的 AuthenticationManager。先給你們一個結論,一個系統中,咱們能夠配置多個 HttpSecurity(參見Spring Security 居然能夠同時存在多個過濾器鏈?),而每個 HttpSecurity 都有一個對應的 AuthenticationManager 實例(局部 AuthenticationManager),這些局部的 AuthenticationManager 實例都有一個共同的 parent,那就是全局的 AuthenticationManager。
接下來,咱們經過源碼分析來驗證咱們上面的結論。
本文內容和上篇文章緊密相關,若是你們還沒看過上篇源碼分析文章,必定點擊超連接先看下。
2.源碼分析
在上篇文章中,鬆哥已經和你們分析了 SecurityBuilder 的幾個常見實現類 AbstractSecurityBuilder、AbstractConfiguredSecurityBuilder、HttpSecurityBuilder,本文關於這幾個類我就不重複介紹了。
咱們直接來看 AuthenticationManagerBuilder,先來看它的一個繼承關係:
![](http://static.javashuo.com/static/loading.gif)
能夠看到,【上篇文章】中介紹的所有都是 AuthenticationManagerBuilder 的父類,因此 AuthenticationManagerBuilder 已經自動具有了其父類的功能。
AuthenticationManagerBuilder 的源碼比較長,咱們來看幾個關鍵的方法:
public class AuthenticationManagerBuilder
extends
AbstractConfiguredSecurityBuilder<AuthenticationManager, AuthenticationManagerBuilder>
implements ProviderManagerBuilder<AuthenticationManagerBuilder> {
public AuthenticationManagerBuilder(ObjectPostProcessor<Object> objectPostProcessor) {
super(objectPostProcessor, true);
}
public AuthenticationManagerBuilder parentAuthenticationManager(
AuthenticationManager authenticationManager) {
if (authenticationManager instanceof ProviderManager) {
eraseCredentials(((ProviderManager) authenticationManager)
.isEraseCredentialsAfterAuthentication());
}
this.parentAuthenticationManager = authenticationManager;
return this;
}
public InMemoryUserDetailsManagerConfigurer<AuthenticationManagerBuilder> inMemoryAuthentication()
throws Exception {
return apply(new InMemoryUserDetailsManagerConfigurer<>());
}
public JdbcUserDetailsManagerConfigurer<AuthenticationManagerBuilder> jdbcAuthentication()
throws Exception {
return apply(new JdbcUserDetailsManagerConfigurer<>());
}
public <T extends UserDetailsService> DaoAuthenticationConfigurer<AuthenticationManagerBuilder, T> userDetailsService(
T userDetailsService) throws Exception {
this.defaultUserDetailsService = userDetailsService;
return apply(new DaoAuthenticationConfigurer<>(
userDetailsService));
}
@Override
protected ProviderManager performBuild() throws Exception {
if (!isConfigured()) {
logger.debug("No authenticationProviders and no parentAuthenticationManager defined. Returning null.");
return null;
}
ProviderManager providerManager = new ProviderManager(authenticationProviders,
parentAuthenticationManager);
if (eraseCredentials != null) {
providerManager.setEraseCredentialsAfterAuthentication(eraseCredentials);
}
if (eventPublisher != null) {
providerManager.setAuthenticationEventPublisher(eventPublisher);
}
providerManager = postProcess(providerManager);
return providerManager;
}
}
-
首先,咱們能夠經過調用 parentAuthenticationManager 方法來給一個 AuthenticationManager 設置 parent。 -
inMemoryAuthentication、jdbcAuthentication 以及 userDetailsService 幾個方法鬆哥在以前的文章中都已經介紹過了( 深刻理解 SecurityConfigurer 【源碼篇】),做用就是爲了配置數據源,這裏就再也不贅述。 -
最後就是 performBuild 方法,這個方法的做用就是根據當前 AuthenticationManagerBuilder 來構建一個 AuthenticationManager 出來,AuthenticationManager 自己是一個接口,它的默認實現是 ProviderManager,因此這裏構建的就是 ProviderManager。在構建 ProviderManager 時,一方面傳入 authenticationProviders,就是該 ProviderManager 所管理的全部的 AuthenticationProvider,另外一方面傳入 ProviderManager 的 parent(其實也是一個 ProviderManager)。
總體來講,這段代碼仍是很好理解的,鬆哥在以前的文章中和你們介紹過 Spring Security 整合多個數據源,那個時候咱們本身配置 ProviderManager,跟這裏的方式相似,具體能夠參考:Spring Security 能夠同時對接多個用戶表?。
不過本身配置有一個問題就是咱們沒有配置 ProviderManager 的 parent,沒有配置的話,若是當前 ProviderManager 中認證失敗的話,就直接拋出失敗,而不會去 parent 中再次進行認證了(通常來講也不須要,若是系統比較複雜的話,可能須要)。
AuthenticationManagerBuilder 還有一個實現類叫作 DefaultPasswordEncoderAuthenticationManagerBuilder,做爲內部類分別定義在 WebSecurityConfigurerAdapter 和 AuthenticationConfiguration 中,不過 DefaultPasswordEncoderAuthenticationManagerBuilder 的內容比較簡單,重寫了父類 AuthenticationManagerBuilder 的幾個方法,配置了新的 PasswordEncoder,無他,因此這裏我就不列出這個的源碼了,感興趣的小夥伴能夠自行查看。可是這並非說 DefaultPasswordEncoderAuthenticationManagerBuilder 就不重要了,由於在後面的使用中,基本上都是使用 DefaultPasswordEncoderAuthenticationManagerBuilder 來構建 AuthenticationManagerBuilder。
好啦,這就是 AuthenticationManagerBuilder。
那麼是何時經過 AuthenticationManagerBuilder 來構建 AuthenticationManager 的呢?
這就涉及到咱們的老熟人 WebSecurityConfigurerAdapter 了。固然,關於 WebSecurityConfigurerAdapter 自己的初始化過程,鬆哥在後面會專門寫文章介紹,今天咱們主要來看下如何在 WebSecurityConfigurerAdapter 中開啓 AuthenticationManager 的初始化的。
2.1 初始化流程
在初始化流程中,鬆哥得先和你們介紹一個 AuthenticationConfiguration 類。這個類你們能夠看成是一個全局被配類來理解,裏邊都是一些全局屬性的配置:
@Configuration(proxyBeanMethods = false)
@Import(ObjectPostProcessorConfiguration.class)
public class AuthenticationConfiguration {
@Bean
public AuthenticationManagerBuilder authenticationManagerBuilder(
ObjectPostProcessor<Object> objectPostProcessor, ApplicationContext context) {
LazyPasswordEncoder defaultPasswordEncoder = new LazyPasswordEncoder(context);
AuthenticationEventPublisher authenticationEventPublisher = getBeanOrNull(context, AuthenticationEventPublisher.class);
DefaultPasswordEncoderAuthenticationManagerBuilder result = new DefaultPasswordEncoderAuthenticationManagerBuilder(objectPostProcessor, defaultPasswordEncoder);
if (authenticationEventPublisher != null) {
result.authenticationEventPublisher(authenticationEventPublisher);
}
return result;
}
@Bean
public static GlobalAuthenticationConfigurerAdapter enableGlobalAuthenticationAutowiredConfigurer(
ApplicationContext context) {
return new EnableGlobalAuthenticationAutowiredConfigurer(context);
}
@Bean
public static InitializeUserDetailsBeanManagerConfigurer initializeUserDetailsBeanManagerConfigurer(ApplicationContext context) {
return new InitializeUserDetailsBeanManagerConfigurer(context);
}
@Bean
public static InitializeAuthenticationProviderBeanManagerConfigurer initializeAuthenticationProviderBeanManagerConfigurer(ApplicationContext context) {
return new InitializeAuthenticationProviderBeanManagerConfigurer(context);
}
public AuthenticationManager getAuthenticationManager() throws Exception {
if (this.authenticationManagerInitialized) {
return this.authenticationManager;
}
AuthenticationManagerBuilder authBuilder = this.applicationContext.getBean(AuthenticationManagerBuilder.class);
if (this.buildingAuthenticationManager.getAndSet(true)) {
return new AuthenticationManagerDelegator(authBuilder);
}
for (GlobalAuthenticationConfigurerAdapter config : globalAuthConfigurers) {
authBuilder.apply(config);
}
authenticationManager = authBuilder.build();
if (authenticationManager == null) {
authenticationManager = getAuthenticationManagerBean();
}
this.authenticationManagerInitialized = true;
return authenticationManager;
}
@Autowired
public void setApplicationContext(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
@Autowired
public void setObjectPostProcessor(ObjectPostProcessor<Object> objectPostProcessor) {
this.objectPostProcessor = objectPostProcessor;
}
private static class EnableGlobalAuthenticationAutowiredConfigurer extends
GlobalAuthenticationConfigurerAdapter {
private final ApplicationContext context;
private static final Log logger = LogFactory
.getLog(EnableGlobalAuthenticationAutowiredConfigurer.class);
EnableGlobalAuthenticationAutowiredConfigurer(ApplicationContext context) {
this.context = context;
}
@Override
public void init(AuthenticationManagerBuilder auth) {
Map<String, Object> beansWithAnnotation = context
.getBeansWithAnnotation(EnableGlobalAuthentication.class);
if (logger.isDebugEnabled()) {
logger.debug("Eagerly initializing " + beansWithAnnotation);
}
}
}
}
-
這裏首先構建了一個 AuthenticationManagerBuilder 實例,這個實例就是用來構建全局 AuthenticationManager 的 AuthenticationManagerBuilder,具體的構建過程在下面的 getAuthenticationManager 方法中。不過這裏的這個全局的 AuthenticationManagerBuilder 並不是老是有用,爲何這麼說呢?且看鬆哥下面的的分析。 -
另外還有一些 initializeXXX 方法,用來構建全局的 UserDetailService 和 AuthenticationProvider,這些方法小夥伴能夠做爲一個瞭解,由於正常狀況下是不會用到這幾個 Bean 的,只有當 getAuthenticationManager 方法被調用時,這些默認的 Bean 纔會被配置,而 getAuthenticationManager 方法被調用,意味着咱們要使用系統默認配置的 AuthenticationManager 做爲 parent,而在實際使用中,咱們通常不會使用系統默認配置的 AuthenticationManager 做爲 parent,咱們本身多多少少都會從新定製一下。
這就是 AuthenticationConfiguration 的主要功能,它主要是提供了一些全局的 Bean,這些全局的 Bean 雖然必定會初始化,可是並不是必定用到。
那麼到底何時用到,何時用不到,這就和 WebSecurityConfigurerAdapter 有關了,在 WebSecurityConfigurerAdapter 中有三個重要的方法涉及到 AuthenticationManager 的初始化問題,第一個是 setApplicationContext 方法:
public void setApplicationContext(ApplicationContext context) {
this.context = context;
ObjectPostProcessor<Object> objectPostProcessor = context.getBean(ObjectPostProcessor.class);
LazyPasswordEncoder passwordEncoder = new LazyPasswordEncoder(context);
authenticationBuilder = new DefaultPasswordEncoderAuthenticationManagerBuilder(objectPostProcessor, passwordEncoder);
localConfigureAuthenticationBldr = new DefaultPasswordEncoderAuthenticationManagerBuilder(objectPostProcessor, passwordEncoder) {
@Override
public AuthenticationManagerBuilder eraseCredentials(boolean eraseCredentials) {
authenticationBuilder.eraseCredentials(eraseCredentials);
return super.eraseCredentials(eraseCredentials);
}
@Override
public AuthenticationManagerBuilder authenticationEventPublisher(AuthenticationEventPublisher eventPublisher) {
authenticationBuilder.authenticationEventPublisher(eventPublisher);
return super.authenticationEventPublisher(eventPublisher);
}
};
}
在該方法中,建立了兩個幾乎一摸同樣的 AuthenticationManagerBuilder 實例,爲何會有兩個呢?第一個 authenticationBuilder 是一個局部的 AuthenticationManagerBuilder,未來會傳入 HttpSecurity 中去構建局部的 AuthenticationManager;第二個 localConfigureAuthenticationBldr 則是一個用來構建全局 AuthenticationManager 的 AuthenticationManagerBuilder。
有小夥伴會問了,構建全局的 AuthenticationManager 不是一開始就在 AuthenticationConfiguration 中建立了嗎?爲何這裏還有一個?是的,當前這個 localConfigureAuthenticationBldr 是能夠禁用的,若是禁用了,就會使用 AuthenticationConfiguration 中提供的 AuthenticationManagerBuilder,若是沒禁用,就使用 localConfigureAuthenticationBldr 來構建全局的 AuthenticationManager。
另外一個方法則是 getHttp 方法:
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);
//省略
return http;
}
在 getHttp 方法中,會首先調用 authenticationManager 方法去獲取一個全局的 AuthenticationManager,並設置給 authenticationBuilder 做爲 parent,而後在構建 HttpSecurity 時將 authenticationBuilder 傳入進去。
那麼接下來就是 authenticationManager() 方法究竟是怎麼執行的了:
protected AuthenticationManager authenticationManager() throws Exception {
if (!authenticationManagerInitialized) {
configure(localConfigureAuthenticationBldr);
if (disableLocalConfigureAuthenticationBldr) {
authenticationManager = authenticationConfiguration
.getAuthenticationManager();
}
else {
authenticationManager = localConfigureAuthenticationBldr.build();
}
authenticationManagerInitialized = true;
}
return authenticationManager;
}
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
this.disableLocalConfigureAuthenticationBldr = true;
}
能夠看到,若是 AuthenticationManager 還沒初始化,那就先進行初始化。初始化首先調用 configure 方法,默認狀況下,configure 方法裏邊會把 disableLocalConfigureAuthenticationBldr 變量設置爲 true,這樣接下來就會進入到 if 分支中了。這個 configure 方法不知道你們有沒有以爲眼熟?咱們在自定義的 SecurityConfig 配置類中,通常都是要重寫該方法的,一旦重寫了這個方法,那麼 disableLocalConfigureAuthenticationBldr 變量就不會變爲 true,依然是 false,這樣在獲取 authenticationManager 的時候就會進入到 else 分支中。
若是進入到 if 分支中,意味着開發者並無重寫 configure 方法,AuthenticationManagerBuilder 就使用默認的,你們能夠看到,此時就是調用 authenticationConfiguration.getAuthenticationManager() 方法去獲取 AuthenticationManager,也就是一開始咱們說的那個全局的配置。
若是開發者重寫了 configure 方法,意味着開發者對 AuthenticationManagerBuilder 進行了一些定製,此時就不能繼續使用 AuthenticationConfiguration 中配置的默認的的 AuthenticationManager 了,而要根據開發者 的具體配置,調用 localConfigureAuthenticationBldr.build 方法去構建新的 AuthenticationManager。
一言以蔽之,AuthenticationConfiguration 中的配置有沒有用上,全看開發者有沒有重寫 configure(AuthenticationManagerBuilder auth)
方法,重寫了,就用 localConfigureAuthenticationBldr 來構建 parent 級別的 AuthenticationManager,沒重寫,就用 AuthenticationConfiguration 中的方法來構建。
這是扮演 parent 角色的 AuthenticationManager 的構建過程,固然,parent 並不是必須,若是你沒有這個需求的話,也能夠不配置 parent。
最後咱們再來看下局部的 AuthenticationManager 是如何構建的,也就是和 HttpSecurity 綁定的那個 AuthenticationManager。
根據前面的介紹,HttpSecurity 在構建的時候就會傳入 AuthenticationManagerBuilder,以下:
public HttpSecurity(ObjectPostProcessor<Object> objectPostProcessor,
AuthenticationManagerBuilder authenticationBuilder,
Map<Class<?>, Object> sharedObjects) {
super(objectPostProcessor);
Assert.notNull(authenticationBuilder, "authenticationBuilder cannot be null");
setSharedObject(AuthenticationManagerBuilder.class, authenticationBuilder);
//省略
}
傳入進來的 AuthenticationManagerBuilder ,二話不說就存到 SharedObject 裏邊去了,這個根據官方的註釋,說它是一個在不一樣 Configurer 中共享的對象的工具,其實你能夠理解爲一個緩存,如今存進去,須要的時候再取出來。
取出來的方法,在 HttpSecurity 中也定義好了,以下:
private AuthenticationManagerBuilder getAuthenticationRegistry() {
return getSharedObject(AuthenticationManagerBuilder.class);
}
在 HttpSecurity 中,凡是涉及到 AuthenticationManager 配置的,都會調用到 getAuthenticationRegistry 方法,以下:
public HttpSecurity userDetailsService(UserDetailsService userDetailsService)
throws Exception {
getAuthenticationRegistry().userDetailsService(userDetailsService);
return this;
}
public HttpSecurity authenticationProvider(
AuthenticationProvider authenticationProvider) {
getAuthenticationRegistry().authenticationProvider(authenticationProvider);
return this;
}
最後在 HttpSecurity 的 beforeConfigure 方法中完成構建:
@Override
protected void beforeConfigure() throws Exception {
setSharedObject(AuthenticationManager.class, getAuthenticationRegistry().build());
}
至此,不管是全局的 AuthenticationManager,仍是局部的 AuthenticationManager,就都和你們捋一遍了。
3.小結
有的小夥伴可能對這裏的全局、局部不是特別理解,我再給你們稍微總結一下。
爲何每個 HttpSecurity 都要綁定一個 AuthenticationManager?
由於在同一個系統中,咱們能夠回配置多個 HttpSecurity,也就是多個不一樣的過濾器鏈(參見Spring Security 居然能夠同時存在多個過濾器鏈?一文),既然有多個過濾器鏈,每個請求到來的時候,它須要進入到某一個過濾器鏈中去處理,每個過濾器鏈中又會涉及到 AuthenticationProvider 的管理,不一樣過濾器鏈中的 AuthenticationProvider 確定是各自管理最爲合適,也就是不一樣的過濾器鏈中都有一個綁定的 AuthenticationManager,即每個 HttpSecurity 都要綁定一個 AuthenticationManager。
本文略有難度,可能有點繞,有沒看懂的地方,歡迎你們留言討論。
若是小夥伴們以爲有收穫,記得點個在看鼓勵下鬆哥哦~
今日干貨
公衆號後臺回覆 2TB,免費獲取 2TB Java 學習資料。
本文分享自微信公衆號 - 江南一點雨(a_javaboy)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。