共计 6309 个字符,预计需要花费 16 分钟才能阅读完成。
这一节咱们以后面默认的 OAuth2 客户端集成为例,来理解下配置文件的加载,示例见第二、第三节。
源码剖析
InMemoryClientRegistrationRepository
如果你没有看过相干视频,或者书,但想要本人剖析源码,应该怎么剖析?
在剖析原理之前,咱们肯定要找到突破口,否则就会无从下手,突破口就是之前集成 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
咱们点进去,外部就是一个 OAuth2ClientProperties
类,这个类配置了 @ConfigurationProperties
注解用来加载配置文件,用 IDE 查找一下该类用在了哪些地方,进去很多类,在这种没法一下判断的状况下,我的方法就是一个个进去看,判断哪个类最有可能,Reactive 结尾的类都是在响应式环境下应用的,都能够疏忽。
这里 OAuth2ClientRegistrationRepositoryConfiguration
就是咱们要找的类,在该类中会加载一个 InMemoryClientRegistrationRepository
Bean,该 Bean 用于本地存储客户端注册信息的。
@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);
}
}
这里有如下几个配置:
- @Configuration(proxyBeanMethods = false):应用 @Bean 时候的配置,proxyBeanMethods 示意是否应用代理来获取 bean,这里示意不应用代理获取,这样配置可能进步 Spring 的加载速度。
- @EnableConfigurationProperties:开启
OAuth2ClientProperties
Spring Bean - @Conditional(ClientsConfiguredCondition.class): 只有在存在
ClientsConfiguredCondition
Bean 的时候,才注册该类
InMemoryClientRegistrationRepository
Bean 只有在 ClientRegistrationRepository
不存在的时候才会加载。
该 Bean 的流程是从 OAuth2ClientProperties
配置中获取 OAuth 客户端信息,构建 ClientRegistration
对象,并存储在 InMemoryClientRegistrationRepository
中。
这个类看似如同到这里就完了,线索断了吗,其实没有,OAuth 客户端配置的加载的确是实现了,那前面其余类必定会应用到该配置类,这个前面在看,别忘记咱们的问题。
回到 OAuth2ClientRegistrationRepositoryConfiguration
所在的目录,你会发现该目录下还有两个文件 OAuth2ClientAutoConfiguration
和 OAuth2WebSecurityConfiguration
,
看下 OAuth2ClientAutoConfiguration
类,原来 OAuth2ClientRegistrationRepositoryConfiguration
也是由它疏导加载的,那么咱们看下另外一个类。
OAuth2WebSecurityConfiguration
OAuth2WebSecurityConfiguration
类中,注册了 InMemoryOAuth2AuthorizedClientService
、OAuth2AuthorizedClientRepository
、SecurityFilterChain
。
(1)InMemoryOAuth2AuthorizedClientService
是 OAuth2AuthorizedClientService 的实现,用于本地保留 OAuth2 受权客户端,具备保留已认证的受权客户端(saveAuthorizedClient)、移除已认证的受权客户端(removeAuthorizedClient)和获取已认证的受权客户端(loadAuthorizedClient)3 个性能。
在该类中,你会发现保留了 ClientRegistrationRepository 对象,并且 loadAuthorizedClient 和 removeAuthorizedClient 的时候,都会调用 ClientRegistrationRepository 中的 findByRegistrationId 办法,至此又跟后面加载的 InMemoryClientRegistrationRepository 分割在了一起。
(2)AuthenticatedPrincipalOAuth2AuthorizedClientRepository
是 OAuth2AuthorizedClientRepository 的实现,用于保护 principal
主体(了解为已认证的用户)与受权客户端 OAuth2AuthorizedClient 的关系,并且提供了一个匿名的解决,如果是匿名应用 HttpSessionOAuth2AuthorizedClientRepository 解决 (也可笼罩提供)。
该类提供了 loadAuthorizedClient、saveAuthorizedClient、removeAuthorizedClient、setAnonymousAuthorizedClientRepository 几个公开办法
(3)SecurityFilterChain
:一个过滤器链,用来匹配申请,匹配的申请将执行一系列过滤器。
@Bean
SecurityFilterChain oauth2SecurityFilterChain(HttpSecurity http) throws Exception {http.authorizeRequests((requests) -> requests.anyRequest().authenticated()); http.oauth2Login(Customizer.withDefaults()); http.oauth2Client(); return http.build(); }
代码可见,外部应用 HttpSecurity 构建了一个默认的 SecurityFilterChain,表明任何申请都能够应用该过滤器链,应用 oauth2 提供的默认登录形式(提供一个 /login 的默认登录页面),再最初 http.build() 用于构建一个 SecurityFilterChain,看看此处代码。
http.build(),build() 位于 HttpSecurity 的父类 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 来保障构建的对象只会构建一次,咱们次要看 doBuild(),其是一个形象办法,用于子类去实现具体的构建逻辑,该子类是 AbstractConfiguredSecurityBuilder。
protected final O doBuild() throws Exception {synchronized (this.configurers) {
// 标记构建状态
this.buildState = BuildState.INITIALIZING;
// 加载配置前的解决,默认空实现,子类能够笼罩实现
beforeInit();
// 加载配置
init();
// 批改构建状态
this.buildState = BuildState.CONFIGURING;
// 在开始配置之前的解决
beforeConfigure();
// 开始配置,调用实现了 SecurityConfigurer 的 configure()
// 在这里会将各种内置的过滤器增加到 HttpSecurity 中
configure();
this.buildState = BuildState.BUILDING;
// 开始构建要返回的对象,形象返回,子类实现构建逻辑
O result = performBuild();
this.buildState = BuildState.BUILT;
return result;
}
}
HttpSecurity 的构建逻辑如下:
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);
}
此处先判断是否同时加载了 ExpressionUrlAuthorizationConfigurer(基于 SpEL 的 URL 受权)和 AuthorizeHttpRequestsConfigurer(应用 AuthorizationManager 增加基于 URL 的受权,该类是 5.5 新增),这两个不能同时应用。
而后再对加载的过滤器进行 Order 排序,最初生成 DefaultSecurityFilterChain 对象返回。
咱们能够看下此处 filters 的值,发现曾经加载了 18 个 filter,如下,其中 OAuth2 结尾的几个过滤器特地显眼。
DisableEncodeUrlFilter
WebAsyncManagerIntegrationFilter
SecurityContextPersistenceFilter
HeaderWriterFilter
CsrfFilter
LogoutFilter
OAuth2AuthorizationRequestRedirectFilter
OAuth2AuthorizationRequestRedirectFilter
OAuth2LoginAuthenticationFilter
DefaultLoginPageGeneratingFilter
DefaultLogoutPageGeneratingFilter
RequestCacheAwareFilter
SecurityContextHolderAwareRequestFilter
AnonymousAuthenticationFilter
OAuth2AuthorizationCodeGrantFilter
SessionManagementFilter
ExceptionTranslationFilter
FilterSecurityInterceptor
到这里,Spring Security OAuth2 的默认配置曾经加载完了,这里形容内容只是咱们表象能看到的,其实还有其余的内容,比方 HttpSecurity 等还有很多。
前面咱们将深入分析这 18 个过滤器都干了哪些事。
要学习一个新框架,我个别会依照如下步骤来施行:
(1)依据官网文档搭建 Demo,先跑起来,有一个整体观
(2)剖析源码,从 Demo 的性能配置动手,找到突破口
(3)每次剖析源码,要带着问题看
(4)依据源码剖析进去的思路,画架构图、流程图
(5)学习框架的实现思路,取其精华去其糟粕