前言
这一节我们要以前面默认的 OAuth2 客户端集成为例,来了解下配置文件的加载。
配置文件加载
假如你没有看过大佬视频,或者书,但想要自己分析源码,应该怎么分析?在分析原理之前,我们一定要找到突破口,否则就会无从下手,突破口就是之前集成 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 查找一下该类用在了哪些地方,出来很多类,在这种没法一下判断的情况下,我的办法就是一个个进去看,判断哪个类最有可能。
这里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
类中,注册了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 个过滤器都干了哪些事。
评论