spring OAuth2

本文深入探讨OAuth2协议在Spring框架中的实现细节,包括ResourceServer如何处理带有access_token的请求,以及授权服务器如何处理Token请求。从TokenEndpoint的处理流程,到OAuth2AuthenticationProcessingFilter的认证过程,再到AuthenticationManager的配置与作用,全面解析OAuth2的工作机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Spring 

ResourceServerTokenServices->TokenStore->v

OAuth2AuthenticationProceessingFilter implemets Filter
protect resources,从请求中获取OAuth2 token,使用token 获取OAuth2Authentication 放入Security context

doFilter方法首先调用tokenExtractor.extract(request)从请求头中获取token,默认的实现类为BearerTokenExtractor,获取的是一个
PreAuthenticatedAuthenticationToken,principal 为token,然后调用 AuthenticationManager.authenticate方法进行认证,
实现类是 OAuth2AuthenticationManager,从参数authentication中获取principal后,调用ResourceServerTokenServices.loadAuthentication
方法获取OAuth2Authentication(类似AutheticationProvider->AbstractUserDetailsAuthenticationProvider->DaoAuthenticationProvider.retrieveUser->
UserDetailsService.loadUserByUsername),默认实现DefaultTokenServies(implemets AuthorizationServerTokenServices,ResourceServerTokenServices
前一个获取OAuth2AccessToken,后一个获取OAuth2Authentication相关的),进入方法的第一步是调  
实现有Jdbc,redis,jwt,inMemory,JwtTokenStore.readAccessToken方法会调用JwtTokenEnhancer.extractAccessToken(String tokenValue)->
JwtTokenEnhancer.extractAccessToken(tokenValue, JwtTokenEnhancer.decode(tokenValue)) ,JwtTokenEnhancer的接口为TokenEnhancer,实现类JwtAccessTokenCover implements TokenEnhancer,AccessTokenCoverter,decode方法获取jwtToken中的claims(JWTHelper,SignatureVerifier),封装成Map对象,
所以jwtTokenEnhancer.extractAccessToken(string,map)->继续调用AccessTokenConverter(DefaultAccessTokenConverter).extractAccessToken方法,
先 new DefaultOAuth2AccessToken(jwtToken),设置expire,JTI,scope,additionalInfo(实际是原来的Map claims 中去除了exp,aud,client_id,scope信息),
继续ResourceServerTokenServices(DefaultTokenServices).loadAuthentication方法中,获取上面的OAuth2AccessToken之后,再次使用同一个TokenStore,
调用readAuthetication(OAuth2AccessToken参数仅仅使用了OAuthAccessToken中token,也就是原来request中的token)->JwtTokenStore.readAuthentication(String token)
->jwtTokenEnhancer.extractAuthentication(jwtTokenEnhancer.decode(token))->JwtAccessTokenConverter.extractAuthentication(map)->
tokenConverter(DefaultAccessTokenConverter).extractAuthentication(map)该方法内部会使用UserAuthenticationConverter(Default...).extractAuthentication(map)
该方法会使用UserDetailsService获取用户信息(返回的Authention(UsernamePasswordAuthenticationToken)),最终AccessTokenConverter返回的OAuth2Auenticaiton对象
为OAuth2Authentication(request(clientId,authorities,scope,resourceIds...),UserPwAuthenticationToken),DefaultTokenServie获取OAuth2Authentiation之后
会校验一下clientId对应的ClientDetails是否存在,OAuth2AuthenticationManager获取OAuth2Authentication之后会checkClientDetails(auth) scope检测,然后设置Authenticaion true,OAuth2AuthenticationProcessingFilter获取Authentication后设置到SecurityContextHolder中(ThreadLocal),到此结束


ClientCredentialsTokenEndpointFilter
TokenEndpoint
TokenRequest->OAuth2AccessToken->getResponse(OAuth2AccessToken)
TokenGranter.grant(grant_type,TokenRequest),tokenGranter 是AuthorizationServerEndpointsConfigurer的内部TokenGranter实现类,
默认使用的是CompositeTokenGranter组合形式,此处使用的是grant_type=password ,所以TokenGranter是ResourceOwnerPasswordTokenGranter
通过ResourceOwnerPasswordTokenGranter的父类AbstractTokenGranter.grant->getAccessToken->tokenServices.createAccessToken(OAuth2Authentication)
来获取OAuth2AccessToken,首先获取OAuth2Authentication,调用ResourceOwnerPsswordTokenGranter获取OAuth2Authnetication方法,该方法
从TokenRequest中的params获取username,password实例化UsernamePasswordAuthentication对象,调用AuthenticationManager.authenticate方法做认证
,认证通过,调用DenfaultOAuth2RequestFactory->TokenRequest.createOAuth2Request(ClientDetails)来构造一个OAuth2Request,去除了一些sensitive info
password,client_secret,构建的OAuth2Request包含map,clientId,authorities,scope,resourceId,OAuth2Request和认证过的Authentication(UsernamePasswordAuthentication)用来构造OAuth2Authentication,然后AuthorizationServerTokenServices(DefaultTokenServices).createAccessToken(OAuth2Authentication)通过OAuth2Authentication 获取OAuth2AccessToken,先调用tokenStore(此处配置的为InMemoryTokenStore).getAccessToken(authenticaiton),
首先通过keyGenerator.extractkey(OAuth2Authentication)生成key,从内存中ConcurrentHashMap来获取OAuth2AccessToken,DefaultTokenService.createAccessToken
通过InMemeoryTokenStore获取accessToken后,判断accessToken是否存在以及是否expired,expired从内从中删除,refreshToken(如果refreshToken!=null 删除refreshToken),未expired的re-store accessToken,refresshToken做expired验证,保证创建的refreshToken未过期, accessToken不存在的话,createAccessToken(authentication,refreshToken)来创建,new DefaultOAuth2AccessToken(UUID.toString),设置有效时间以及refreshToken和从Authentication(OAuth2Request)中获取的
scope,判断AccessTokenEnhancer不为空,调用enchance方法,用以携带更多的信息,创建的这个AccessToken会被存储在ConcurrentHashMap中,最终在TokenEndpoint作为response
返回给调用端

主要步骤:TokenEndpoint 处理AuthServer相关请求
1,oauth/token,通过clientId 得到ClientDetails,加上Map<>params(clientId,clientSecret,grant_type...)来构造TokenRequest,通过TokenRequest校验scope,
通过TokenGranter.grant(String grant_type,tokenRequest)获取OAuth2AccessToken,最关键的方法就是这个,默认的TokenGranter为CompositeTokenGranter,
所有的TokenGranter extend AbstractTokenGranter,AbstractToken.grant通过先获得OAuth2Authentication,在通过tokenServices.createAccessToken(OAuth2Authentication)得到AccessToken,获取Authenticaiton的方法可以被子类重写,默认的为通过DefaultOauth2RequestFactor.createOAuth2Request(client,tokenRequest),
tokenServices的类型为AuthorizationServerTokenServices,默认的实现为DefaultTokenServices,同时也实现了ResourceServerTokenServices,获取AccessToken关键点为
TokenStore,如果为InMemeoryTokenStore,JdbcTokenStore等直接从持久层获取,第一次的话会new DefaultOAuth2AccessToken(UUID)并持久化,如果是JwtTokenStore的话,
AccessToken对应的Authenticaiton并不会持久化,所以在new DefaultOAuth2AccessToken后通过TokenEnhancer.enhance(token,authenticaiton)方法处理(将value uuid set jwtToken形式),TokenEnhancer的实现有JwtAccessTokenConverter和TokenEnhancerChain,JwtTokenAccessTokenConverter.enhance方法通过encode方法来构造需要的JWT的claim
和header,claim会包含aud,jti,scope,client_id,exp,可以重写该方法将自定义的参数设置到AccessToken参数中去,经过JwtHelper.encode之后设置到AccessToken的value中,refreshToken也会做同样的处理

ResourceServer 处理带有access_token的请求
OAuth2AuthenticationProcessingFilter:
extractor.extract(request)构造一个PreAuthenticatedAuthenticationToken authentication,AuthenticatioManager.authenticate(authentication),
ResourceServer构造的为OAuth2AuthenticationManager,OAuth2AuthenticationManager.authenticate方法中调用
ResourceServerTokenServices tokenServices.loadAuthentication(String token),和上面的为同一个,且都实现了对应的接口,
DefaultTokenServces.loadAuthentication方法,通过TokenStore.readAccessToken(String accessTokenValue)获取OAuth2AccessToken,JwtTokenStore的话会解析toke,
解析出claim设置到additionalInfo中,以及一些必要的属性,在通过TokenStore.readAuthentication(OAuth2AccessToken)获取OAuth2Authentication,JwtTokenStore
会通过jwtTokenEnhancer.extractAuthentication(jwtTokenEnhancer.decode(token))最终获得OAuth2Authencation并设置到SecurityContextHolder中.

------------------------关于 AuthenticationManager 和 HttpSecurity-------------------------------------
AuthenticationManager直接有AuthenticationManagerBuilder.build构建,AMB包含了List<AuthenticationProvider>,AuthenticationProvider有support和authenticate
2个接口用于处理specific Authentication,比如DaoAuthenticationProvider 用来处理UsernamePasswrodAuthentication的.
AuthenticationConfiguration 猜测是用来配置获取global AuthenticationManager的,包含List<GlobalAuthenticationConfigurerAdapter>
WebSecurityConfigurerAdapter 拥有AuthenticationConfiguration,AuthenticationManagerBuilder,当我们继承该WebSecurityConfigurerAdapter时,会重写
configure(HttpSecurity http)方法,由于WebSecurityConfigurerAdapter实现了SecurityConfigurer接口,会先调用init方法,在调用configure方法,WebSecurityConfigurerAdapter只实现了init方法,
init方法调用getHttp()来获取HttpSecurity来创建SecurityFilterChain,并且添加FilterSecurityInterceptor(熟悉security的应该知道这个的作用),
getHttp()方法,重要步骤
1获取AuthenticationManager,调用authenticationManager(),判断是否authenticationManagerInitialized,true,直接返回,false的话,先设置disableLocalConfigurerAuthenticationBldr为true,判断disableLocalConfigurerAuthenticationBldr,true,从
authenticationConfiguration.getAuthenticationManager()获取全局AuthenticationManager,false,调用localConfiguerAuthenticationBldr.build()
从本地获取(提示一旦我们重写了WebSecurityConfigurerAdapter的configure(AuthenticationManagerBuilder auth),意味着我们采用localConfigurerAuthenticationBldr)
,后期在调用HttpSecurity.beforeConfigure时会从sharedObject中获取AuthenticationManagerBuilder,build()后获得AuthenticationManager在设置到SharedObject.
2.获取AuthenticationManager之后,将其设置为authenticationManagerBuilder的parentAuthenticationManager,然后new HttpSecurity(),该方法主要是
将UserDetailsService,AuthenticationTrustResolver,AuthenticatioManagerBuilder设置为SharedObject,
3.如果采用默认的话,2步获取的httpSecurity会配置一些默认的csrf,WebAsncManagerIntegrationFilter,anonymous,logout等filter,并配置defaultHttpConfigurers
4.调用configure(HttpSecurity http)就是我们重写的配置
5.返回httpSecurity给init方法
#########################################
说明:像BasicAuthenticationFilter,ClientCredentialsTokenEndpointFilter,UsernamePasswordAuthenticationFilter中的AuthenticationManager都是在对应的
FilterConfigurer中通过httpSecurity.getSharedObject(AuthenticationManager)获取的,并且每个SecurityFilterChain都有自己的AuthenticationManager
所以属于同一个SecurityFilterChain的 AuthenticationManager应该是WebSecurityConfigurerAdapter中的localConfigureAuthenticationBldr(如果重写configure(AuthenticationManager)方法,否则就是gloabl配置的AuthenticationManager)

关于AuthenticationManager的实现类 默认的为ProviderManager,ProiderManager 中的AuthenticationProvier,一般默认是DaoAuthenticationProvider包含了
UserDetailsService(像授权服务器的BasicAuthenticationFilter中的为ClientDetailsUserDetailsService,这个会在@EnableAuthorizationServer中自动配,ResourceOwnerPasswordTokenGranter中的为JdbcDaoImpl或者默认的InMemoryUserDetailsManager)
在资源服务器的配置中@EnableResurceServer AuthenticationManager 会自动配置为OAuth2AuthenticationManager,所以不会采用global AuthenticationManger,


再来说一下grant_type=password和authorization_code获取toke流程的区别,访问oauth/token
password:默认的auth server 没有开启ClientCredentialsTokenEndpointFilter,开启的话会位于BasicAuthenticationFilter之前,他们中的AuthenticationManager
都是来认证client_id和client_secret的,认证通过后,TokenEndpoint这个controller会处理请求返回OAuth2AccessToken包含访问令牌,简化了获取code的过程,这个获取
Token的主要区别在于TokenGranter的不同,password的为ResourceOwnerPasswordTokenGranter,这个会做一次AuthenticationManager.authenticate,这个一般会验证
用户名和密码这种组合,最后根据TokenEnhancer是否存在将默认的OAuth2AcceeToken做一些转换,如使用JwtTokenConverter


authorization_code:首先调用oauth/authorize?response_type=code&scope=&client_id=&redirect_uri 后去授权,在BasicAuthenticationFilter中AuthenticationManager中默认的provider是
AnonymousAuthenticationProvider(因为该请求不在授权服务器的filterchain中)这个认证显然不会通过,但AuthenticationManager会有调用ParentAuthenticationManager中provider也就是global AuthenticationManagerDaoAuthenticationProvider UserDetailsService 认证用户名密码组合,然后AuthorizationEndpoint controller 处理/oauth/authorize请求,有2个方法,一个处理带user_oauth_approval另一个不带,不带的会返回一个询问是否授权的页面,并会设置AuthorizationRequest(由Factory 参数params 构造) 到session中,如果同意授权,带user_oauth_approval的方法处理该请求,会生成一个code,通过AuthorizationCodeServices 生成并保存在内存或数据库中,返回给页面,redirect redirect_uri,所以redirect_uri要设置,得到authorization code之后,调用/oauth/token?grant_type=authorization_code&code=&scope=&redirect_uri 去获取访问令牌,此时请求在授权服务器的filterChain中会做client_id,client_secret(设置到Authentication头或者url中)组合认证,通过之后TokenEndpoint处理,此时的TokenGranter为AuthorizationCodeTokenGranter,重写了getOAuth2Authention方法从AuthorizationCodeServices中获取,code去处之后就删除因此只能用一次,然后在父类AbstractTokenGranter
调用TokenServices根据OAuth2Authentication生成OAuth2AccessToken,不过这个不会带有refresh_token,因为这种防止获取token必须经过用户授权获取code才能去申请Token,
refresh_token也就没意义了

DefaultAccessTokenCoverter.extractAuthentication,首先会调用UserTokenConverter.extractAuthenticaion(map)
->UserDetailsService.loadUserByUsername返回Authentication(存在的话,对象也会包含authorities),最后new OAuth2Request 和Authenticaiton一起放入new 的
OAuth2Authentication中,

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值