您好,登錄后才能下訂單哦!
這篇文章主要介紹“spring security中AuthenticationManagerBuilder怎么理解”,在日常操作中,相信很多人在spring security中AuthenticationManagerBuilder怎么理解問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”spring security中AuthenticationManagerBuilder怎么理解”的疑惑有所幫助!接下來,請跟著小編一起來學習吧!
在 Spring Security 中,用來處理身份認證的類是 AuthenticationManager,我們也稱之為認證管理器。
AuthenticationManager 中規(guī)范了 Spring Security 的過濾器要如何執(zhí)行身份認證,并在身份認證成功后返回一個經(jīng)過認證的 Authentication 對象。AuthenticationManager 是一個接口,我們可以自定義它的實現(xiàn),但是通常我們使用更多的是系統(tǒng)提供的 ProviderManager。
ProviderManager 是的最常用的 AuthenticationManager 實現(xiàn)類。
ProviderManager 管理了一個 AuthenticationProvider 列表,每個 AuthenticationProvider 都是一個認證器,不同的 AuthenticationProvider 用來處理不同的 Authentication 對象的認證。一次完整的身份認證流程可能會經(jīng)過多個 AuthenticationProvider。
ProviderManager 相當于代理了多個 AuthenticationProvider,他們的關系如下圖:
AuthenticationProvider 定義了 Spring Security 中的驗證邏輯,我們來看下 AuthenticationProvider 的定義:
public interface AuthenticationProvider {
Authentication authenticate(Authentication authentication)
throws AuthenticationException;
boolean supports(Class<?> authentication);
}
可以看到,AuthenticationProvider 中就兩個方法:
在一次完整的認證中,可能包含多個 AuthenticationProvider,而這多個 AuthenticationProvider 則由 ProviderManager 進行統(tǒng)一管理。
最常用的 AuthenticationProvider 實現(xiàn)類是 DaoAuthenticationProvider。
每一個 ProviderManager 管理多個 AuthenticationProvider,同時每一個 ProviderManager 都可以配置一個 parent,如果當前的 ProviderManager 中認證失敗了,還可以去它的 parent 中繼續(xù)執(zhí)行認證,所謂的 parent 實例,一般也是 ProviderManager,也就是 ProviderManager 的 parent 還是 ProviderManager。可以參考如下架構圖:
從上面的分析中大家可以看出,AuthenticationManager 的初始化會分為兩塊,一個全局的 AuthenticationManager,也就是 parent,另一個則是局部的 AuthenticationManager。先給大家一個結論,一個系統(tǒng)中,我們可以配置多個 HttpSecurity(參見Spring Security 竟然可以同時存在多個過濾器鏈?),而每一個 HttpSecurity 都有一個對應的 AuthenticationManager 實例(局部 AuthenticationManager),這些局部的 AuthenticationManager 實例都有一個共同的 parent,那就是全局的 AuthenticationManager。
接下來,我們通過源碼分析來驗證我們上面的結論。
在上篇文章中,松哥已經(jīng)和大家分析了 SecurityBuilder 的幾個常見實現(xiàn)類 AbstractSecurityBuilder、AbstractConfiguredSecurityBuilder、HttpSecurityBuilder,本文關于這幾個類我就不重復介紹了。
我們直接來看 AuthenticationManagerBuilder,先來看它的一個繼承關系:
可以看到, AuthenticationManagerBuilder 已經(jīng)自動具備了其父類的功能。
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;
}
}
不過自己配置有一個問題就是我們沒有配置 ProviderManager 的 parent,沒有配置的話,如果當前 ProviderManager 中認證失敗的話,就直接拋出失敗,而不會去 parent 中再次進行認證了(一般來說也不需要,如果系統(tǒng)比較復雜的話,可能需要)。
AuthenticationManagerBuilder 還有一個實現(xiàn)類叫做 DefaultPasswordEncoderAuthenticationManagerBuilder,作為內部類分別定義在 WebSecurityConfigurerAdapter 和 AuthenticationConfiguration 中,不過 DefaultPasswordEncoderAuthenticationManagerBuilder 的內容比較簡單,重寫了父類 AuthenticationManagerBuilder 的幾個方法,配置了新的 PasswordEncoder,無他,所以這里我就不列出這個的源碼了,感興趣的小伙伴可以自行查看。但是這并不是說 DefaultPasswordEncoderAuthenticationManagerBuilder 就不重要了,因為在后面的使用中,基本上都是使用 DefaultPasswordEncoderAuthenticationManagerBuilder 來構建 AuthenticationManagerBuilder。
好啦,這就是 AuthenticationManagerBuilder。
那么是什么時候通過 AuthenticationManagerBuilder 來構建 AuthenticationManager 的呢?
這就涉及到我們的老熟人 WebSecurityConfigurerAdapter 了。今天我們主要來看下如何在 WebSecurityConfigurerAdapter 中開啟 AuthenticationManager 的初始化的。
在初始化流程中,松哥得先和大家介紹一個 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);
}
}
}
}
這就是 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);
}
};
}
在該方法中,創(chuàng)建了兩個幾乎一摸一樣的 AuthenticationManagerBuilder 實例,為什么會有兩個呢?第一個 authenticationBuilder 是一個局部的 AuthenticationManagerBuilder,將來會傳入 HttpSecurity 中去構建局部的 AuthenticationManager;第二個 localConfigureAuthenticationBldr 則是一個用來構建全局 AuthenticationManager 的 AuthenticationManagerBuilder。
有小伙伴會問了,構建全局的 AuthenticationManager 不是一開始就在 AuthenticationConfiguration 中創(chuàng)建了嗎?為什么這里還有一個?是的,當前這個 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() 方法到底是怎么執(zhí)行的了:
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 變量就不會變?yōu)?true,依然是 false,這樣在獲取 authenticationManager 的時候就會進入到 else 分支中。
如果進入到 if 分支中,意味著開發(fā)者并沒有重寫 configure 方法,AuthenticationManagerBuilder 就使用默認的,大家可以看到,此時就是調用 authenticationConfiguration.getAuthenticationManager() 方法去獲取 AuthenticationManager,也就是一開始我們說的那個全局的配置。
如果開發(fā)者重寫了 configure 方法,意味著開發(fā)者對 AuthenticationManagerBuilder 進行了一些定制,此時就不能繼續(xù)使用 AuthenticationConfiguration 中配置的默認的的 AuthenticationManager 了,而要根據(jù)開發(fā)者 的具體配置,調用 localConfigureAuthenticationBldr.build 方法去構建新的 AuthenticationManager。
一言以蔽之,AuthenticationConfiguration 中的配置有沒有用上,全看開發(fā)者有沒有重寫 configure(AuthenticationManagerBuilder auth)
方法,重寫了,就用 localConfigureAuthenticationBldr 來構建 parent 級別的 AuthenticationManager,沒重寫,就用 AuthenticationConfiguration 中的方法來構建。
這是扮演 parent 角色的 AuthenticationManager 的構建過程,當然,parent 并非必須,如果你沒有這個需求的話,也可以不配置 parent。
最后我們再來看下局部的 AuthenticationManager 是如何構建的,也就是和 HttpSecurity 綁定的那個 AuthenticationManager。
根據(jù)前面的介紹,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 里邊去了,這個根據(jù)官方的注釋,說它是一個在不同 Configurer 中共享的對象的工具,其實你可以理解為一個緩存,現(xiàn)在存進去,需要的時候再取出來。
取出來的方法,在 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());
}
到此,關于“spring security中AuthenticationManagerBuilder怎么理解”的學習就結束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學習,快去試試吧!若想繼續(xù)學習更多相關知識,請繼續(xù)關注億速云網(wǎng)站,小編會繼續(xù)努力為大家?guī)砀鄬嵱玫奈恼拢?/p>
免責聲明:本站發(fā)布的內容(圖片、視頻和文字)以原創(chuàng)、轉載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權內容。