OAuth Client默认配置加载

简介: 这一节我们要以前面默认的OAuth2 客户端集成为例,来了解下配置文件的加载。

前言

这一节我们要以前面默认的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查找一下该类用在了哪些地方,出来很多类,在这种没法一下判断的情况下,我的办法就是一个个进去看,判断哪个类最有可能。
image.png
这里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:一个过滤器链,用来匹配请求,匹配的请求将执行一系列过滤器。

@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个过滤器都干了哪些事。

相关文章
|
6月前
|
XML 应用服务中间件 Apache
Tomcat AJP连接器配置secretRequired=“true“,但是属性secret确实空或者空字符串,这样的组合是无效的。
Tomcat AJP连接器配置secretRequired=“true“,但是属性secret确实空或者空字符串,这样的组合是无效的。
|
3月前
|
安全 Java 应用服务中间件
【Azure 应用服务】App Service中,为Java应用配置自定义错误页面,禁用DELETE, PUT方法
【Azure 应用服务】App Service中,为Java应用配置自定义错误页面,禁用DELETE, PUT方法
【Azure 应用服务】App Service中,为Java应用配置自定义错误页面,禁用DELETE, PUT方法
|
3月前
|
API
【Azure 应用服务】在App Service中调用外部服务API时需要携带客户端证书,而多次调用的情况下会出现WindowsCryptographicException Keyset does not exist异常
【Azure 应用服务】在App Service中调用外部服务API时需要携带客户端证书,而多次调用的情况下会出现WindowsCryptographicException Keyset does not exist异常
|
3月前
【Azure 云服务】Azure Cloud Service 为 Web Role(IIS Host)增加自定义字段 (把HTTP Request Header中的User-Agent字段增加到IIS输出日志中)
【Azure 云服务】Azure Cloud Service 为 Web Role(IIS Host)增加自定义字段 (把HTTP Request Header中的User-Agent字段增加到IIS输出日志中)
|
3月前
|
存储 安全 API
【Azure API 管理】在APIM中使用客户端证书验证API的请求,但是一直提示错误"No client certificate received."
【Azure API 管理】在APIM中使用客户端证书验证API的请求,但是一直提示错误"No client certificate received."
|
Web App开发 缓存 前端开发
前端项目根据环境设置请求地址和接口代理,以及解决多个localhost服务token被覆盖
一般开发项目除了正式的生产环境,还会有对应的开发环境、测试环境和预发布环境,每个环境所访问的接口地址肯定不一样,如果自己一个个手动修改那就太不程序猿了
637 1
|
API 图形学
U3D客户端框架之实现基于UnityWebRequest的Http服务 实现HttpCallBackArgs参数类、HttpRoutine访问器、HttpManager管理器
Unity3D 在2018版本中弃用了WWW请求,使用UnityWebRequest 进行网络请求,这个方法是为了满足今天的 HTTP 通信的需求,而且诞生的新类,相对于WWW这个方法,会更灵活一些,但是用起来却很不方便。
U3D客户端框架之实现基于UnityWebRequest的Http服务 实现HttpCallBackArgs参数类、HttpRoutine访问器、HttpManager管理器
|
前端开发 JavaScript 编译器
第三十三章 使用 CSP 进行基于标签的开发 - 使用Hyperevents #server和#call调用服务器端方法
第三十三章 使用 CSP 进行基于标签的开发 - 使用Hyperevents #server和#call调用服务器端方法
111 0
|
SQL JavaScript 前端开发
第三十六章 使用 CSP 进行基于标签的开发 - 使用尽可能少的#server和#call调用
第三十六章 使用 CSP 进行基于标签的开发 - 使用尽可能少的#server和#call调用
128 0
|
存储 前端开发 搜索推荐
spring-authorization-server令牌放发源码解析
spring-authorization-server令牌放发源码解析
509 0