Spring?Security?OAuth?Client配置加載源碼解析
引言
這一節(jié)我們以前面默認(rèn)的OAuth2 客戶(hù)端集成為例,來(lái)了解下配置文件的加載,示例見(jiàn)第二、第三節(jié)。
InMemoryClientRegistrationRepository
假如你沒(méi)有看過(guò)相關(guān)視頻,或者書(shū),但想要自己分析源碼,應(yīng)該怎么分析?
在分析原理之前,我們一定要找到突破口,否則就會(huì)無(wú)從下手,突破口就是之前集成Gitee OAuth的配置文件,我們分析任何框架的源碼都是如此,從表象到骨髓,一層層深入。
spring: security: oauth2: client: registration: gitee: client-id: gitee-client-id client-secret: gitee-client-secret authorization-grant-type: authorization_code redirect-uri: '{baseUrl}/login/oauth2/code/{registrationId}' client-name: Gitee github: client-id: b4713d47174917b34c28 client-secret: 898389369c2e9f3d1d0ff4543ba1d9b45adfd093 provider: gitee: authorization-uri: https://gitee.com/oauth/authorize token-uri: https://gitee.com/oauth/token user-info-uri: https://gitee.com/api/v5/user user-name-attribute: name
我們點(diǎn)進(jìn)去,內(nèi)部就是一個(gè) OAuth2ClientProperties
類(lèi),這個(gè)類(lèi)配置了 @ConfigurationProperties
注解用來(lái)加載配置文件,用IDE查找一下該類(lèi)用在了哪些地方,出來(lái)很多類(lèi),在這種沒(méi)法一下判斷的情況下,我的辦法就是一個(gè)個(gè)進(jìn)去看,判斷哪個(gè)類(lèi)最有可能,Reactive開(kāi)頭的類(lèi)都是在響應(yīng)式環(huán)境下使用的,都可以忽略。
這里 OAuth2ClientRegistrationRepositoryConfiguration
就是我們要找的類(lèi),在該類(lèi)中會(huì)加載一個(gè) InMemoryClientRegistrationRepository
Bean,該Bean用于本地存儲(chǔ)客戶(hù)端注冊(cè)信息的。
@Configuration(proxyBeanMethods = false) @EnableConfigurationProperties(OAuth2ClientProperties.class) @Conditional(ClientsConfiguredCondition.class) class OAuth2ClientRegistrationRepositoryConfiguration { @Bean @ConditionalOnMissingBean(ClientRegistrationRepository.class) InMemoryClientRegistrationRepository clientRegistrationRepository(OAuth2ClientProperties properties) { List<ClientRegistration> registrations = new ArrayList<>( OAuth2ClientPropertiesRegistrationAdapter.getClientRegistrations(properties).values()); return new InMemoryClientRegistrationRepository(registrations); } }
配置
這里有如下幾個(gè)配置:
- @Configuration(proxyBeanMethods = false):使用@Bean時(shí)候的配置,proxyBeanMethods表示是否使用代理來(lái)獲取bean,這里表示不使用代理獲取,這樣配置能夠提高Spring 的加載速度。
- @EnableConfigurationProperties:開(kāi)啟
OAuth2ClientProperties
Spring Bean - @Conditional(ClientsConfiguredCondition.class): 只有在存在
ClientsConfiguredCondition
Bean的時(shí)候,才注冊(cè)該類(lèi)
InMemoryClientRegistrationRepository
Bean只有在 ClientRegistrationRepository
不存在的時(shí)候才會(huì)加載。
該Bean的流程是從 OAuth2ClientProperties
配置中獲取OAuth客戶(hù)端信息,構(gòu)建 ClientRegistration
對(duì)象,并存儲(chǔ)在 InMemoryClientRegistrationRepository
中。
這個(gè)類(lèi)看似好像到這里就完了,線(xiàn)索斷了嗎,其實(shí)沒(méi)有,OAuth客戶(hù)端配置的加載確實(shí)是完成了,那后面其他類(lèi)肯定會(huì)使用到該配置類(lèi),這個(gè)后面在看,別忘記我們的問(wèn)題。
回到 OAuth2ClientRegistrationRepositoryConfiguration
所在的目錄,你會(huì)發(fā)現(xiàn)該目錄下還有兩個(gè)文件 OAuth2ClientAutoConfiguration
和 OAuth2WebSecurityConfiguration
,
看下 OAuth2ClientAutoConfiguration
類(lèi),原來(lái) OAuth2ClientRegistrationRepositoryConfiguration
也是由它引導(dǎo)加載的,那么我們看下另外一個(gè)類(lèi)。
OAuth2WebSecurityConfiguration
OAuth2WebSecurityConfiguration
類(lèi)中,注冊(cè)了 InMemoryOAuth2AuthorizedClientService
、 OAuth2AuthorizedClientRepository
、 SecurityFilterChain
。
(1) InMemoryOAuth2AuthorizedClientService
是OAuth2AuthorizedClientService的實(shí)現(xiàn),用于本地保存OAuth2授權(quán)客戶(hù)端,具有保存已認(rèn)證的授權(quán)客戶(hù)端(saveAuthorizedClient)、移除已認(rèn)證的授權(quán)客戶(hù)端(removeAuthorizedClient)和獲取已認(rèn)證的授權(quán)客戶(hù)端(loadAuthorizedClient)3個(gè)功能。
在該類(lèi)中,你會(huì)發(fā)現(xiàn)保存了ClientRegistrationRepository對(duì)象,并且loadAuthorizedClient 和 removeAuthorizedClient 的時(shí)候,都會(huì)調(diào)用ClientRegistrationRepository中的findByRegistrationId方法,至此又跟前面加載的InMemoryClientRegistrationRepository聯(lián)系在了一起。
(2) AuthenticatedPrincipalOAuth2AuthorizedClientRepository
是OAuth2AuthorizedClientRepository的實(shí)現(xiàn),用于維護(hù) principal
主體(理解為已認(rèn)證的用戶(hù))與授權(quán)客戶(hù)端OAuth2AuthorizedClient的關(guān)系,并且提供了一個(gè)匿名的處理,如果是匿名使用HttpSessionOAuth2AuthorizedClientRepository處理(也可覆蓋提供)。
該類(lèi)提供了loadAuthorizedClient、saveAuthorizedClient、removeAuthorizedClient、setAnonymousAuthorizedClientRepository 幾個(gè)公開(kāi)方法
(3) SecurityFilterChain
:一個(gè)過(guò)濾器鏈,用來(lái)匹配請(qǐng)求,匹配的請(qǐng)求將執(zhí)行一系列過(guò)濾器。
@Bean SecurityFilterChain oauth2SecurityFilterChain(HttpSecurity http) throws Exception { http.authorizeRequests((requests) -> requests.anyRequest().authenticated()); http.oauth2Login(Customizer.withDefaults()); http.oauth2Client(); return http.build(); }
代碼可見(jiàn),內(nèi)部使用HttpSecurity構(gòu)建了一個(gè)默認(rèn)的SecurityFilterChain,表明任何請(qǐng)求都可以使用該過(guò)濾器鏈,使用oauth2提供的默認(rèn)登錄方式(提供一個(gè)/login的默認(rèn)登錄頁(yè)面),再最后http.build()用于構(gòu)建一個(gè)SecurityFilterChain,看看此處代碼。
http.build()
build()位于HttpSecurity的父類(lèi)AbstractSecurityBuilder
public final O build() throws Exception { if (this.building.compareAndSet(false, true)) { this.object = doBuild(); return this.object; } throw new AlreadyBuiltException("This object has already been built"); }
build()使用了CAS來(lái)保證構(gòu)建的對(duì)象只會(huì)構(gòu)建一次,我們主要看doBuild(),其是一個(gè)抽象方法,用于子類(lèi)去實(shí)現(xiàn)具體的構(gòu)建邏輯,該子類(lèi)是AbstractConfiguredSecurityBuilder。
protected final O doBuild() throws Exception { synchronized (this.configurers) { //標(biāo)記構(gòu)建狀態(tài) this.buildState = BuildState.INITIALIZING; //加載配置前的處理,默認(rèn)空實(shí)現(xiàn),子類(lèi)可以覆蓋實(shí)現(xiàn) beforeInit(); //加載配置 init(); //修改構(gòu)建狀態(tài) this.buildState = BuildState.CONFIGURING; //在開(kāi)始配置之前的處理 beforeConfigure(); //開(kāi)始配置,調(diào)用實(shí)現(xiàn)了SecurityConfigurer的configure() //在這里會(huì)將各種內(nèi)置的過(guò)濾器添加到HttpSecurity中 configure(); this.buildState = BuildState.BUILDING; //開(kāi)始構(gòu)建要返回的對(duì)象,抽象返回,子類(lèi)實(shí)現(xiàn)構(gòu)建邏輯 O result = performBuild(); this.buildState = BuildState.BUILT; return result; } }
HttpSecurity的構(gòu)建邏輯
protected DefaultSecurityFilterChain performBuild() { ExpressionUrlAuthorizationConfigurer<?> expressionConfigurer = getConfigurer( ExpressionUrlAuthorizationConfigurer.class); AuthorizeHttpRequestsConfigurer<?> httpConfigurer = getConfigurer(AuthorizeHttpRequestsConfigurer.class); boolean oneConfigurerPresent = expressionConfigurer == null ^ httpConfigurer == null; Assert.state((expressionConfigurer == null && httpConfigurer == null) || oneConfigurerPresent, "authorizeHttpRequests cannot be used in conjunction with authorizeRequests. Please select just one."); this.filters.sort(OrderComparator.INSTANCE); List<Filter> sortedFilters = new ArrayList<>(this.filters.size()); for (Filter filter : this.filters) { sortedFilters.add(((OrderedFilter) filter).filter); } return new DefaultSecurityFilterChain(this.requestMatcher, sortedFilters); }
此處先判斷是否同時(shí)加載了ExpressionUrlAuthorizationConfigurer(基于SpEL的URL授權(quán))和AuthorizeHttpRequestsConfigurer(使用AuthorizationManager添加基于 URL 的授權(quán),該類(lèi)是5.5新增),這兩個(gè)不能同時(shí)使用。
然后再對(duì)加載的過(guò)濾器進(jìn)行Order排序,最后生成DefaultSecurityFilterChain對(duì)象返回。
我們可以看下此處filters的值,發(fā)現(xiàn)已經(jīng)加載了18個(gè)filter,如下,其中OAuth2開(kāi)頭的幾個(gè)過(guò)濾器特別顯眼。
DisableEncodeUrlFilter
WebAsyncManagerIntegrationFilter
SecurityContextPersistenceFilter
HeaderWriterFilter
CsrfFilter
LogoutFilter
OAuth2AuthorizationRequestRedirectFilter
OAuth2AuthorizationRequestRedirectFilter
OAuth2LoginAuthenticationFilter
DefaultLoginPageGeneratingFilter
DefaultLogoutPageGeneratingFilter
RequestCacheAwareFilter
SecurityContextHolderAwareRequestFilter
AnonymousAuthenticationFilter
OAuth2AuthorizationCodeGrantFilter
SessionManagementFilter
ExceptionTranslationFilter
FilterSecurityInterceptor
到這里,Spring Security OAuth2 的默認(rèn)配置已經(jīng)加載完了,這里描述內(nèi)容只是我們表象能看到的,其實(shí)還有其他的內(nèi)容,比如HttpSecurity等還有很多。
后面我們將深入分析這18個(gè)過(guò)濾器都干了哪些事。
要學(xué)習(xí)一個(gè)新框架,我一般會(huì)按照如下步驟來(lái)實(shí)施:
(1)根據(jù)官方文檔搭建Demo,先跑起來(lái),有一個(gè)整體觀(guān)
(2)分析源碼,從Demo的功能配置入手,找到突破口
(3)每次分析源碼,要帶著問(wèn)題看
(4)根據(jù)源碼分析出來(lái)的思路,畫(huà)架構(gòu)圖、流程圖
(5)學(xué)習(xí)框架的實(shí)現(xiàn)思路,取其精華去其糟粕
以上就是Spring Security OAuth Client配置加載源碼解析的詳細(xì)內(nèi)容,更多關(guān)于Spring Security OAuth Client配置加載的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
- 注冊(cè)中心配置了spring?security后客戶(hù)端啟動(dòng)報(bào)錯(cuò)
- SpringBoot?整合Security權(quán)限控制的初步配置
- Spring?Security自定義登錄頁(yè)面認(rèn)證過(guò)程常用配置
- SpringBoot2.7?WebSecurityConfigurerAdapter類(lèi)過(guò)期配置
- Spring Security實(shí)現(xiàn)禁止用戶(hù)重復(fù)登陸的配置原理
- spring security動(dòng)態(tài)配置url權(quán)限的2種實(shí)現(xiàn)方法
- SpringBoot + Spring Security 基本使用及個(gè)性化登錄配置詳解
相關(guān)文章
java使用正則表達(dá)式過(guò)濾html標(biāo)簽
本篇文章主要介紹了java正則表達(dá)式過(guò)濾html標(biāo)簽,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2016-11-11JAVA不使用線(xiàn)程池來(lái)處理的異步的方法詳解
這篇文章主要介紹了JAVA不使用線(xiàn)程池來(lái)處理的異步的方法,在這個(gè)示例中,asyncTask方法創(chuàng)建了一個(gè)新的線(xiàn)程來(lái)執(zhí)行異步任務(wù),這個(gè)新線(xiàn)程會(huì)立即開(kāi)始執(zhí)行,而主線(xiàn)程則會(huì)繼續(xù)執(zhí)行后續(xù)的代碼,感興趣的朋友跟隨小編一起看看吧2024-05-05

Java?項(xiàng)目連接并使用?SFTP?服務(wù)的示例詳解

TransmittableThreadLocal通過(guò)javaAgent實(shí)現(xiàn)線(xiàn)程傳遞并支持ForkJoin

mybatis插件pageHelper實(shí)現(xiàn)分頁(yè)效果

Android應(yīng)用開(kāi)發(fā)的一般文件組織結(jié)構(gòu)講解