这一节咱们以后面默认的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 所在的目录,你会发现该目录下还有两个文件 OAuth2ClientAutoConfigurationOAuth2WebSecurityConfiguration ,

看下 OAuth2ClientAutoConfiguration 类,原来 OAuth2ClientRegistrationRepositoryConfiguration 也是由它疏导加载的,那么咱们看下另外一个类。

OAuth2WebSecurityConfiguration

OAuth2WebSecurityConfiguration 类中,注册了 InMemoryOAuth2AuthorizedClientServiceOAuth2AuthorizedClientRepositorySecurityFilterChain

(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 :一个过滤器链,用来匹配申请,匹配的申请将执行一系列过滤器。

@BeanSecurityFilterChain 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结尾的几个过滤器特地显眼。

DisableEncodeUrlFilterWebAsyncManagerIntegrationFilterSecurityContextPersistenceFilterHeaderWriterFilterCsrfFilterLogoutFilterOAuth2AuthorizationRequestRedirectFilterOAuth2AuthorizationRequestRedirectFilterOAuth2LoginAuthenticationFilterDefaultLoginPageGeneratingFilterDefaultLogoutPageGeneratingFilterRequestCacheAwareFilterSecurityContextHolderAwareRequestFilterAnonymousAuthenticationFilterOAuth2AuthorizationCodeGrantFilterSessionManagementFilterExceptionTranslationFilterFilterSecurityInterceptor

到这里,Spring Security OAuth2 的默认配置曾经加载完了,这里形容内容只是咱们表象能看到的,其实还有其余的内容,比方HttpSecurity等还有很多。

前面咱们将深入分析这18个过滤器都干了哪些事。

要学习一个新框架,我个别会依照如下步骤来施行:

(1)依据官网文档搭建Demo,先跑起来,有一个整体观

(2)剖析源码,从Demo的性能配置动手,找到突破口

(3)每次剖析源码,要带着问题看

(4)依据源码剖析进去的思路,画架构图、流程图

(5)学习框架的实现思路,取其精华去其糟粕