• Spring Security OAuth Client配置加载源码分析


    活动地址:CSDN21天学习挑战赛

    相关文章:

    1. OAuth2的定义和运行流程
    2. Spring Security OAuth实现Gitee快捷登录
    3. Spring Security OAuth实现GitHub快捷登录
    4. Spring Security的过滤器链机制

    前言

    这一节我们以前面默认的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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    我们点进去,内部就是一个OAuth2ClientProperties类,这个类配置了@ConfigurationProperties 注解用来加载配置文件,用IDE查找一下该类用在了哪些地方,出来很多类,在这种没法一下判断的情况下,我的办法就是一个个进去看,判断哪个类最有可能,Reactive开头的类都是在响应式环境下使用的,都可以忽略。

    在这里插入图片描述

    这里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);
    	}
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    这里有如下几个配置:

    • @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

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

    @Bean
    SecurityFilterChain oauth2SecurityFilterChain(HttpSecurity http) throws Exception {
        http.authorizeRequests((requests) -> requests.anyRequest().authenticated());
        http.oauth2Login(Customizer.withDefaults());
        http.oauth2Client();
        return http.build();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    代码可见,内部使用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");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    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;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    此处先判断是否同时加载了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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    到这里,Spring Security OAuth2 的默认配置已经加载完了,这里描述内容只是我们表象能看到的,其实还有其他的内容,比如HttpSecurity等还有很多。

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

    总结

    要学习一个新框架,我一般会按照如下步骤来实施:
    (1)根据官方文档搭建Demo,先跑起来,有一个整体观
    (2)分析源码,从Demo的功能配置入手,找到突破口
    (3)每次分析源码,要带着问题看
    (4)根据源码分析出来的思路,画架构图、流程图
    (5)学习框架的实现思路,取其精华去其糟粕

    参考博主:码农小胖哥的2022版PDF

  • 相关阅读:
    Spark在大数据集群下的部署
    案例+图解带你一文读懂Canvas【2W字,建议收藏】
    黑眼圈:缓解/防止方法
    1677. 发票中的产品金额
    SQL Server大量插入 Java
    二维码智慧门牌管理系统:创新历史,稳定未来
    (附源码)springboot物流配货管理系统 毕业设计 250858
    谈谈分布式事务原理
    从数学到算法
    【git】取消git代理
  • 原文地址:https://blog.csdn.net/weixin_40972073/article/details/126296562