当前位置: 首页 > Web前端 > HTML

SpringSecurityOAuth客户端配置加载源码分析

时间:2023-03-28 15:47:17 HTML

本节我们以前面默认的OAuth2客户端集成为例,来了解一下配置文件的加载。有关示例,请参见第二和第三部分。源码分析InMemoryClientRegistrationRepository如果没有看过相关的视频或者书籍,但是想自己分析源码,应该怎么分析呢?在分析原理之前,一定要找到突破口,否则就无从下手。突破口是之前集成GiteeOAuth的配置文件。我们对任何一个框架的源码,从表象到骨髓,层层剖析。弹簧:安全:oauth2:客户端:注册:gitee:客户端id:gitee-client-id客户端秘密:gitee-client-secret授权-授予类型:authorization_coderedirect-uri:'{baseUrl}/login/oauth2/code/{registrationId}'client-name:Giteegithub:client-id:b4713d47174917b34c28client-secret:898389369c2e9f3d1d0ff4543ba1d9b45adfd093provider:gitee:authorization-uri:https://gitee.com/oauthen-authorize:https://gitee.com/oauthen-authorizegitee.com/oauth/tokenuser-info-uri:https://gitee.com/api/v5/useruser-name-attribute:name我们点进去,里面是一个OAuth2ClientProperties类,它配置了@ConfigurationProperties注解,用来加载配置文件,用IDE查找类在哪里使用,很多类就出来了。在这种我一下子无法判断的情况下,我的方法是一个一个进去判断最有可能是哪个类,Reactive开头的类都是在响应式环境下使用的,可以忽略。这里OAuth2ClientRegistrationRepositoryConfiguration就是我们要找的类,在这个类中会加载一个InMemoryClientRegistrationRepositoryBean,用于在本地存储客户端注册信息。@Configuration(proxyBeanMethods=false)@EnableConfigurationProperties(OAuth2ClientProperties.class)@Conditional(ClientsConfiguredCondition.class)classOAuth2ClientRegistrationRepositoryConfiguration{@Bean@ConditionalOnMissingBean(ClientRegistrationRepository.class)InMemoryClientRegistrationRepositoryclientRegistrationRepository(OAuth2ClientList注册属性){@Bean@ConditionalOnMissingBean(ClientRegistrationRepository.class)(OAuth2ClientPropertiesRegistrationAdapter.getClientRegistrations(properties).values());返回新的InMemoryClientRegistrationRepository(注册);}}这里有如下配置:@Configuration(proxyBeanMethods=false):当使用@Bean配置时,proxyBeanMethods表示是否使用代理来获取bean,这里表示不使用代理来获取,这样可以提高配置Spring的加载速度。@EnableConfigurationProperties:开启OAuth2ClientPropertiesSpringBean@Conditional(ClientsConfiguredCondition.class):只有当ClientsConfiguredConditionBean存在时才注册这个类。InMemoryClientRegistrationRepositoryBean只有在ClientRegistrationRepository不存在时才会被加载。这个bean的过程是从OAuth2ClientProperties配置中获取OAuth客户端信息,构造ClientRegistration对象,存放在InMemoryClientRegistrationRepository中。这节课好像就到这里了。线索断了吗?事实上,它不是。OAuth客户端配置的加载确实完成了。那么其他类肯定会用到这个配置类。我们稍后再看。不要忘记我们的问题。回到OAuth2ClientRegistrationRepositoryConfiguration所在目录,会发现该目录下有OAuth2ClientAutoConfiguration和OAuth2WebSecurityConfiguration两个文件。查看OAuth2ClientAutoConfiguration类。原来OAuth2ClientRegistrationRepositoryConfiguration也是由它bootload的,那我们再看一个类。OAuth2WebSecurityConfiguration在OAuth2WebSecurityConfiguration类中,注册了InMemoryOAuth2AuthorizedClientService、OAuth2AuthorizedClientRepository、SecurityFilterChain。(1)InMemoryOAuth2AuthorizedClientService是OAuth2AuthorizedClientService的实现,用于本地保存OAuth2授权客户端。loadAuthorizedClient)3个函数。在这个类中,你会发现保存了ClientRegistrationRepository对象,在loadAuthorizedClient和removeAuthorizedClient时,会调用ClientRegistrationRepository中的findByRegistrationId方法,至此与之前加载的InMemoryClientRegistrationRepository关联起来。(2)AuthenticatedPrincipalOAuth2AuthorizedClientRepository是OAuth2AuthorizedClientRepository的实现,用于维护主体主体(理解为认证用户)与授权客户端OAuth2AuthorizedClient之间的关系,提供匿名过程。如果是匿名的,使用HttpSessionOAuth2AuthorizedClientRepository处理(也可以重写)。该类提供loadAuthorizedClient、saveAuthorizedClient、removeAuthorizedClient、setAnonymousAuthorizedClientRepository几个公共方法(3)SecurityFilterChain:用于匹配请求的过滤器链,匹配的请求会执行一系列的过滤器。@BeanSecurityFilterChainoauth2SecurityFilterChain(HttpSecurityhttp)throwsException{http.authorizeRequests((requests)->requests.anyRequest().authenticated());http.oauth2Login(Customizer.withDefaults());http.oauth2Client().http;建造();}代码可见,内部使用HttpSecurity构建一个默认的SecurityFilterChain,表示任何请求都可以使用过滤链,使用oauth2提供的默认登录方式(提供/login的默认登录页面),最后http.build()用于构建SecurityFilterChain,请参阅此处的代码。http.build(),build()位于HttpSecurity的父类AbstractSecurityBuilderpublicfinalObuild()throwsException{if(this.building.compareAndSet(false,true)){this.object=doBuild();返回这个对象;}thrownewAlreadyBuiltException("Thisobjecthasalreadybeenbuilt");}build()使用CAS确保构建的对象只会被构建一次。我们主要看doBuild(),它是子类的一个抽象方法,实现具体的构造逻辑,子类是AbstractConfiguredSecurityBuilder。protectedfinalOdoBuild()throwsException{synchronized(this.configurers){//标记构建状态this.buildState=BuildState.INITIALIZING;//加载配置前的处理,默认空实现,子类可以重写beforeInit();//加载配置init();//修改构建状态this.buildState=BuildState.CONFIGURING;//配置开始前的流程beforeConfigure();//开始配置,调用实现SecurityConfigurer的configure()//这里会在HttpSecurityconfigure()中添加各种内置过滤器;this.buildState=BuildState.BUILDING;//开始构建要返回的对象,抽象返回,子类实现构建逻辑Oresult=performBuild();这。buildState=BuildState.BUILT;返回结果;}}HttpSecurity的构建传递如下:protectedDefaultSecurityFilterChainperformBuild(){ExpressionUrlAuthorizationConfigurerexpressionConfigurer=getConfigurer(ExpressionUrlAuthorizationConfigurer.class);AuthorizeHttpRequestsConfigurerhttpConfigurer=getConfigurer(AuthorizeHttpRequestsConfigurer.班级);booleanoneConfigurerPresent=expressionConfigurer==null^httpConfigurer==null;Assert.state((expressionConfigurer==null&&httpConfigurer==null)||oneConfigurerPresent,"authorizeHttpRequests不能与authorizeRequests结合使用。");this.filters.sort(OrderComparator.INSTANCE);ListsortedFilters=newArrayList<>(this.filters.size());for(Filterfilter:this.filters){sortedFilters.add(((OrderedFilter)filter).filter);}returnnewDefaultSecurityFilterChain(this.requestMatcher,sortedFilters);}这里首先判断是否同时加载了ExpressionUrlAuthorizationConfigurer(基于SpEL的URL授权)和AuthorizeHttpRequestsConfigurer(使用AuthorizationManager添加基于URL的授权)。是5.5新增的),这两个不能同时使用,然后对加载的过滤器进行排序,最后生成一个DefaultSecurityFilterChain对象返回。我们可以看这里filters的值,发现加载了18个filters,如下,其中OAuth2开头的filters尤为显眼。DisableEncodeUrlFilterWebAsyncManagerIntegrationFilterSecurityContextPersistenceFilterHeaderWriterFilterCsrfFilterLogoutFilterOAuth2AuthorizationRequestRedirectFilterOAuth2AuthorizationRequestRedirectFilterOAuth2LoginAuthenticationFilterDefaultLoginPageGeneratingFilterDefaultLogoutPageGeneratingFilterRequestCacheAwareFilterSecurityContextHolderAwareRequestFilterAnonymousAuthenticationFilterOAuth2AuthorizationCodeGrantFilterSessionManagementFilterExceptionTranslationFilterFilterSecurityInterceptor到这里,SpringSecurityOAuth2的默认配置已经加载完了,这里描述内容只是我们表象能看到的,其实还有其他的内容,比如HttpSecurity等还有很多。Laterwewillanalyzeindepthwhatthese18filtersdo.Tolearnanewframework,Igenerallyfollowthestepsbelowtoimplementit:(1)Buildademobasedonofficialdocuments,runitfirst,andhaveanoverallview(2)Analyzethesourcecode,startwiththefunctionalconfigurationofthedemo,andfindabreakthrough(3)Everytimeyouanalyzethesourcecode,youhavetolookatitwithquestions(4)drawarchitecturediagramsandflowchartsbasedontheideasanalyzedfromthesourcecode(5)learntheimplementationideasoftheframework,taketheessenceanddiscardthedross