深入理解 AuthenticationManagerBuilder 【源碼篇】

      網友投稿 1102 2022-05-30

      咱們繼續來擼 Spring Security 源碼。

      前面和大家分享了 SecurityBuilder 以及它的一個重要實現 HttpSecurity,在 SecurityBuilder 的實現類里邊,還有一個重要的分支,那就是 AuthenticationmanagerBuilder,AuthenticationManagerBuilder 看名字就知道是用來構建 AuthenticationManager 的,所以今天我們就來看一看 AuthenticationManager 到底是怎么構建的。

      1.初步理解

      在 Spring Security 中,用來處理身份認證的類是 AuthenticationManager,我們也稱之為認證管理器。

      AuthenticationManager 中規范了 Spring Security 的過濾器要如何執行身份認證,并在身份認證成功后返回一個經過認證的 Authentication 對象。AuthenticationManager 是一個接口,我們可以自定義它的實現,但是通常我們使用更多的是系統提供的 ProviderManager。

      1.1 ProviderManager

      ProviderManager 是的最常用的 AuthenticationManager 實現類。

      ProviderManager 管理了一個 AuthenticationProvider 列表,每個 AuthenticationProvider 都是一個認證器,不同的 AuthenticationProvider 用來處理不同的 Authentication 對象的認證。一次完整的身份認證流程可能會經過多個 AuthenticationProvider。

      ProviderManager 相當于代理了多個 AuthenticationProvider,他們的關系如下圖:

      1.2 AuthenticationProvider

      AuthenticationProvider 定義了 Spring Security 中的驗證邏輯,我們來看下 AuthenticationProvider 的定義:

      public interface AuthenticationProvider { Authentication authenticate(Authentication authentication) throws AuthenticationException; boolean supports(Class authentication); }

      1

      2

      3

      4

      5

      可以看到,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。可以參考如下架構圖:

      從上面的分析中大家可以看出,AuthenticationManager 的初始化會分為兩塊,一個全局的 AuthenticationManager,也就是 parent,另一個則是局部的 AuthenticationManager。先給大家一個結論,一個系統中,我們可以配置多個 HttpSecurity(參見Spring Security 竟然可以同時存在多個過濾器鏈?),而每一個 HttpSecurity 都有一個對應的 AuthenticationManager 實例(局部 AuthenticationManager),這些局部的 AuthenticationManager 實例都有一個共同的 parent,那就是全局的 AuthenticationManager。

      接下來,我們通過源碼分析來驗證我們上面的結論。

      本文內容和上篇文章緊密相關,如果大家還沒看過上篇源碼分析文章,一定點擊超鏈接先看下。

      2.源碼分析

      在上篇文章中,松哥已經和大家分析了 SecurityBuilder 的幾個常見實現類 AbstractSecurityBuilder、AbstractConfiguredSecurityBuilder、HttpSecurityBuilder,本文關于這幾個類我就不重復介紹了。

      我們直接來看 AuthenticationManagerBuilder,先來看它的一個繼承關系:

      可以看到,【上篇文章】中介紹的全部都是 AuthenticationManagerBuilder 的父類,所以 AuthenticationManagerBuilder 已經自動具備了其父類的功能。

      AuthenticationManagerBuilder 的源碼比較長,我們來看幾個關鍵的方法:

      public class AuthenticationManagerBuilder extends AbstractConfiguredSecurityBuilder implements ProviderManagerBuilder { public AuthenticationManagerBuilder(ObjectPostProcessor objectPostProcessor) { super(objectPostProcessor, true); } public AuthenticationManagerBuilder parentAuthenticationManager( AuthenticationManager authenticationManager) { if (authenticationManager instanceof ProviderManager) { eraseCredentials(((ProviderManager) authenticationManager) .isEraseCredentialsAfterAuthentication()); } this.parentAuthenticationManager = authenticationManager; return this; } public InMemoryUserDetailsManagerConfigurer inMemoryAuthentication() throws Exception { return apply(new InMemoryUserDetailsManagerConfigurer<>()); } public JdbcUserDetailsManagerConfigurer jdbcAuthentication() throws Exception { return apply(new JdbcUserDetailsManagerConfigurer<>()); } public DaoAuthenticationConfigurer 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; } }

      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      11

      12

      13

      14

      15

      16

      17

      18

      19

      20

      21

      22

      23

      24

      25

      26

      27

      28

      29

      30

      31

      32

      33

      34

      35

      36

      37

      38

      39

      40

      41

      42

      43

      44

      45

      46

      47

      48

      首先,我們可以通過調用 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 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 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 beansWithAnnotation = context .getBeansWithAnnotation(EnableGlobalAuthentication.class); if (logger.isDebugEnabled()) { logger.debug("Eagerly initializing " + beansWithAnnotation); } } } }

      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      11

      12

      13

      14

      15

      16

      17

      18

      19

      20

      21

      22

      23

      24

      25

      26

      27

      28

      29

      30

      31

      32

      33

      34

      35

      36

      37

      38

      39

      40

      41

      42

      43

      44

      45

      46

      47

      48

      49

      50

      51

      52

      53

      54

      55

      56

      57

      58

      59

      60

      61

      62

      63

      64

      65

      66

      67

      68

      69

      70

      71

      72

      73

      74

      75

      76

      77

      78

      79

      80

      81

      82

      83

      84

      85

      這里首先構建了一個 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 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); } }; }

      1

      深入理解 AuthenticationManagerBuilder 【源碼篇】

      2

      3

      4

      5

      6

      7

      8

      9

      10

      11

      12

      13

      14

      15

      16

      17

      18

      在該方法中,創建了兩個幾乎一摸一樣的 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, Object> sharedObjects = createSharedObjects(); http = new HttpSecurity(objectPostProcessor, authenticationBuilder, sharedObjects); //省略 return http; }

      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      11

      12

      13

      14

      在 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; }

      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      11

      12

      13

      14

      15

      16

      17

      可以看到,如果 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 objectPostProcessor, AuthenticationManagerBuilder authenticationBuilder, Map, Object> sharedObjects) { super(objectPostProcessor); Assert.notNull(authenticationBuilder, "authenticationBuilder cannot be null"); setSharedObject(AuthenticationManagerBuilder.class, authenticationBuilder); //省略 }

      1

      2

      3

      4

      5

      6

      7

      8

      傳入進來的 AuthenticationManagerBuilder ,二話不說就存到 SharedObject 里邊去了,這個根據官方的注釋,說它是一個在不同 Configurer 中共享的對象的工具,其實你可以理解為一個緩存,現在存進去,需要的時候再取出來。

      取出來的方法,在 HttpSecurity 中也定義好了,如下:

      private AuthenticationManagerBuilder getAuthenticationRegistry() { return getSharedObject(AuthenticationManagerBuilder.class); }

      1

      2

      3

      在 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; }

      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      最后在 HttpSecurity 的 beforeConfigure 方法中完成構建:

      @Override protected void beforeConfigure() throws Exception { setSharedObject(AuthenticationManager.class, getAuthenticationRegistry().build()); }

      1

      2

      3

      4

      至此,無論是全局的 AuthenticationManager,還是局部的 AuthenticationManager,就都和大家捋一遍了。

      3.小結

      有的小伙伴可能對這里的全局、局部不是特別理解,我再給大家稍微總結一下。

      為什么每一個 HttpSecurity 都要綁定一個 AuthenticationManager?

      因為在同一個系統中,我們可以回配置多個 HttpSecurity,也就是多個不同的過濾器鏈(參見Spring Security 竟然可以同時存在多個過濾器鏈?一文),既然有多個過濾器鏈,每一個請求到來的時候,它需要進入到某一個過濾器鏈中去處理,每一個過濾器鏈中又會涉及到 AuthenticationProvider 的管理,不同過濾器鏈中的 AuthenticationProvider 肯定是各自管理最為合適,也就是不同的過濾器鏈中都有一個綁定的 AuthenticationManager,即每一個 HttpSecurity 都要綁定一個 AuthenticationManager。

      本文略有難度,可能有點繞,有沒看懂的地方,歡迎大家留言討論。

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

      Spring 統一身份認證服務 IAM

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      上一篇:深度學習之圖像識別 核心技術與案例實戰
      下一篇:華為大數據應用分享
      相關文章
      久久亚洲精品无码gv| 亚洲成色www久久网站夜月| 国产精品亚洲不卡一区二区三区 | 亚洲综合伊人制服丝袜美腿| 国产av无码专区亚洲国产精品| 亚洲精品一二三区| 亚洲视频在线不卡| 久久国产精品亚洲综合| 久久亚洲av无码精品浪潮| 亚洲av无码成人精品区| 亚洲国产品综合人成综合网站| 亚洲精品WWW久久久久久| 亚洲第一街区偷拍街拍| 亚洲人成图片网站| 亚洲成人激情小说| 亚洲AV一二三区成人影片| 亚洲首页在线观看| 亚洲精品资源在线| 91亚洲性爱在线视频| 亚洲午夜精品国产电影在线观看| 亚洲国产成人资源在线软件| 亚洲一级毛片中文字幕| 亚洲人成人网毛片在线播放| 亚洲Av永久无码精品黑人| 内射无码专区久久亚洲| 亚洲色图综合在线| 色久悠悠婷婷综合在线亚洲| 亚洲人成色7777在线观看| 亚洲国产成人久久综合一| 亚洲美女激情视频| 国产成人亚洲合集青青草原精品 | MM131亚洲国产美女久久| 亚洲香蕉网久久综合影视| 国产亚洲综合色就色| 亚洲最新永久在线观看| 亚洲一区二区三区久久| 亚洲午夜精品一区二区麻豆| 国产精品观看在线亚洲人成网| 亚洲男人的天堂一区二区| 亚洲国产日韩在线视频| 97亚洲熟妇自偷自拍另类图片|