写点什么

默认配置下 OAuth2 Client 的实现

作者:阿提说说
  • 2022 年 8 月 11 日
    浙江
  • 本文字数:3953 字

    阅读完需:约 13 分钟

默认配置下OAuth2 Client的实现

前言

这一节我们要以前面默认的 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就是我们要找的类,在该类中会加载一个InMemoryClientRegistrationRepositoryBean,该 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:开启OAuth2ClientPropertiesSpring Bean@Conditional(ClientsConfiguredCondition.class): 只有在存在ClientsConfiguredConditionBean 的时候,才注册该类


InMemoryClientRegistrationRepositoryBean 只有在ClientRegistrationRepository不存在的时候才会加载。该 Bean 的流程是从OAuth2ClientProperties配置中获取 OAuth 客户端信息,构建ClientRegistration对象,并存储在InMemoryClientRegistrationRepository中。这个类看似好像到这里就完了,线索断了吗,其实没有,OAuth 客户端配置的加载确实是完成了,那后面其他类肯定会使用到该配置类,这个后面在看。


回到OAuth2ClientRegistrationRepositoryConfiguration所在的目录,你会发现该目录下还有两个文件OAuth2ClientAutoConfigurationOAuth2WebSecurityConfiguration,看下OAuth2ClientAutoConfiguration类,原来OAuth2ClientRegistrationRepositoryConfiguration也是由它引导加载的,那么我们看下另外一个类。


OAuth2WebSecurityConfiguration类中,注册了InMemoryOAuth2AuthorizedClientServiceOAuth2AuthorizedClientRepositorySecurityFilterChain


(1)InMemoryOAuth2AuthorizedClientServiceOAuth2AuthorizedClientService的实现,用于本地保存 OAuth2 授权客户端,具有保存已认证的授权客户端(saveAuthorizedClient)、移除已认证的授权客户端(removeAuthorizedClient)和获取已认证的授权客户端(loadAuthorizedClient)3 个功能。在该类中,你会发现保存了ClientRegistrationRepository对象,并且 loadAuthorizedClient 和 removeAuthorizedClient 的时候,都会调用ClientRegistrationRepository 中的findByRegistrationId方法,至此又跟前面加载的InMemoryClientRegistrationRepository联系在了一起。


(2)AuthenticatedPrincipalOAuth2AuthorizedClientRepositoryOAuth2AuthorizedClientRepository的实现,用于维护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 个过滤器都干了哪些事。


发布于: 刚刚阅读数: 3
用户头像

阿提说说

关注

一年太久,只争朝夕 2017.10.19 加入

还未添加个人简介

评论

发布
暂无评论
默认配置下OAuth2 Client的实现_Spring Security OAuth_阿提说说_InfoQ写作社区