脚本宝典收集整理的这篇文章主要介绍了Spring Security OAuth2 完全解析 (流程/原理/实战定制) —— Client / ResourceServer 篇,脚本宝典觉得挺不错的,现在分享给大家,也给大家做个参考。
本文假设读者对 Spring Security 本身原理有一定程度的了解,假设对 OAuth2 规范流程、Jwt 有基础了解,以此来对 SpringSecurity 整合 OAuth2 有个快速全面的认识。 (关于总体流程,若对SS实在不熟悉可以简单理解为:Filter构造Authentication-> Provider认证并填充-> 设置到SecurityContext -> 而后用于Filter/AOP鉴权)
要了解一个SpringSecurity模块,就是了解它如何身份认证、如何自定义、(至于如何鉴权是SpringSecurity通用部分),对应到代码就是:背后的相关 Configurer、Filter、Authentication、Provider。
本文以最新Spring Boot版本2.6.2 按此逻辑梳理;涉及源码太多,就不放源码对照了,可以自行fork查看;斜体表示可配置自定义替换的部分
由 OAuth2ClientAutoConfiguration
自动配置类引入的默认配置,可由代码 thirdpart-login 复现。
LoginUrlAuthenticationEntryPoint
重定向到登录页。
DefaultLoginPageGeneratingFilter
根据相关配置自动构造页面String返回。
<a "href"="/oauth2/authorization/gitee">
发出授权请求。
后端OAuth2AuthorizationRequestRedirectFilter
匹配响应该模板路径,返回实际授权码请求的重定向响应,转入三方授权页面:
OAuth2LoginAuthenticationFilter
匹配处理,其会用请求携带的 code 向配置的 "token-uri、user-info-uri" 发起一系列请求,最后构造出认证后的身份放入SecurityContext
,以SESSION持久化等。再将先前保存在SESSION中的的受限资源访问请求拿出,重定向重新访问。
这两者都是在SpringSecurity中整合OAuth2的入口方法(例http.oauth2Login()
),对应OAuth2LoginConfigurer
、OAuth2ClientConfigurer
,只是引入Filter有所异同。简而言之:
oauth2Login
会在授权请求时进行认证(即设置安全上下文SecurityContext),背后会连续访问acc_token&user-info-url 将获取的用户信息构造填充 Authentication。oauth2Client
也会对授权请求进行处理,但只是获取到access_token后用repository存起来(要怎么使用自行处理),不会认证,这也意味着需要自行实现认证逻辑。OAuth2AuthorizationRequestRedirectFilter
响应"授权请求"向客户端返回重定向响应,定向到实际 "authorization-uri"OAuth2LoginAuthenticationFilter
会对回调地址(携带了code和state)进行处理,调用AuthemticationManager
进行认证。背后OAuth2LoginAuthenticationProvider
会进行连续 token-uri、user-info-uri 请求,最后返回完全填充的OAuth2LoginAuthenticationToken
。太长就不贴了 参考Github项目代码,CommonOAuth2Provider
也内建了一些常见OAuth2提供方,在之内少配几个字段也没关系。
OAuth2ClientRegistrationRepositoryConfiguration
用以处理application.yml中的相关属性,并构建代表OAuth2方的一个个ClientRegistration
。根据不同模式,对必须属性有不同要求。
userNameAttribute
也是必须的,在后续OAuth2LoginAuthenticationProvider
调用的DefaultOAuth2UserService
内,必须需要这俩属性才能尝试访问 user-info-uri 并包装为DefaultOAuth2User
。authenticated=true
认证后安全上下文实际保存的OAuth2用户认证,由convert
将填充后的OAuth2LoginAuthenticationToken
转换而来。OAuth2AuthorizationRequestRedirectFilter
OAuth2AuthorizationRequestResolver
用于判断是否为授权请求(默认为 "/oauth2/authorization/{registrationId}",可通过.oauth2Login().authorizationEndpoint().baseUri()
配置) ,并且请求包装为OAuth2AuthorizationRequest
后由authorizationRequestRepository
(默认基于SESSION实现)将授权请求保存(后有他用)OAuth2LoginAuthenticationFilter
继承自AbstractAuthenticationProcessingFilter
,即负责身份认证的Filter。
loginProcessingUrl
(默认为/login/oauth2/code/*)请求且带了code和state时,尝试以这俩参数构建OAuth2LoginAuthenticationToken
且调用AuthenticationManager
去进行认证。authenticationResultConverter
将认证后完全填充的OAuth2LoginAuthenticationToken
转为authenticated=true的OAuth2AuthenticationToken
,用以代表认证后的身份。(该converter默认就是直接提取填充后的"principal、authorities、clientid"直接new)OAuth2AuthorizedClient
调用 OAuth2AuthorizedClientRepository#saveAuthorizedClient
保存起来(默认是基于内存实现的ClientId和Principal为key的Map)OAuth2AuthorizationCodeGrantFilter
(该Filter,在oauth2Login()
下会永远被跳过,因为该请求已被OAuth2LoginAuthenticationFilter
处理后通过successHandler
重定向)
匹配带code与state的请求(表示回调请求)且满足authorizationRequestRepository.loadAuthorizationRequest
不为空时(表示经过了RedirectFilter,是先前授权请求发起的),会构造 OAuth2AuthorizationCodeAuthenticationToken
交由AuthenticationManager
(背后交由OAuth2AuthorizationCodeAuthenticationProvider
)进行认证,并将结果构造为OAuth2AuthorizedClient
交由authorizedClientRepository
保存,然后去除参数再将请求重定向到 "savedRequest 或者 redirect-url"。
【注:不是很能理解该Filter这里为什么要重定向,这个重定向真的很恼火。如果API自身需要code,这重定向把参数清除了会报错;而即便API不要code了依附于它的逻辑使用authorizedClientRepository
,那也是无意义多一次请求。而且其基于SESSION的实现本来没什么问题,但非要重定向请求一次就导致单纯的多实例时会存在问题】
OAuth2LoginAuthenticationProvider
OAuth2LoginAuthenticationToken
尝试认证,其内会进一步构造OAuth2AuthorizationCodeAuthenticationToken
,然后调用 OAuth2AuthorizationCodeAuthenticationProvider 对其进行认证。OAuth2AuthorizationCodeAuthenticationToken
,会构造成OAuth2UserRequest
后传给OAuth2UserService
负责进行实际的 "user-info-uri" 请求,并将结果包装成DefaultOAuth2User
返回。(该User拥有两类authorities,一个是ROLE_USER(Spring在经过oauth2UserService时手动添加的),一类是Token中的SCOPE_{sopces})
OAuth2AuthorizationCodeAuthenticationProvider
对OAuth2AuthorizationCodeAuthenticationToken
尝试认证,内部会构造对"token-uri"的实际请求,并调用DefaultAuthorizationCodeTokenResponseClient
进行请求返回,并根据返回结果OAuth2AccessTokenResponse
(内含access_token/refreash_token),新new一个填充了"access_token"的OAuth2AuthorizationCodeAuthenticationToken
返回。
由 org.springframework.boot:spring-boot-starter-oauth2-resource-server
引入,提供对请求中携带token校验解析、身份认证的服务。
oauth2ResourceServer
的Configurer::init
时,会构建JwtAuthenticationProvider
,它就需要decorder
以提供对"token"校验解析。AbstractAuthenticationProcessingFilter
但却干着认证的事)
首先通过DefaultBearerTokenResolver::resolve
判断是否含"token",然后构建BearerTokenAuthenticationToken
并调用AuthenticationManager
尝试认证。
将认证后的结果JwtAuthenticationToken
设置到安全上下文中。如果中途出现了异常,则以该filter的authenticationEntryPoint
(可通过.oauth2ResourceServer().authenticationEntryPoint
配置) 处理。authenticated=true
,进行实际系统访问的身份。由BearerTokenAuthenticationToken
认证后,通过JwtAuthenticationConverter
转换而来。JwtDecoder::decode
(可通过.bearerTokenResolver().jwt().decode
配置)对 "token" 进行解析&验证为Jwt
对象。JwtAuthenticationConverter
(可通过.bearerTokenResolver().jwt().jwtAuthenticationConverter
配置)尝试对Jwt
进一步转换为进行实际系统访问的(authenticated=true)JwtAuthenticationToken
返回。(默认converter
内部会调用jwtGrantedAuthoritiesConverter
解析Jwt
填充 authorities(将"scpoe"/"scp"声明中空格分隔的字串转为SimpleGrantedAuthority
);将"sub"字段作为 principal)实际情况中,除了OAuth2登录,我们系统自身也有完整的用户体系,也有按自己业务定制的token构建分发服务。 三方登录仅作为绑定手段,而且在初次三方登录时 往往还需补全信息注册到我们自己的用户体系。
最终实现代码以及效果展示都放在Github上了:spring-security-oauth2-sample
登录流程,大致API流程:
从上文也能看出,不得不提 笔者实际用SpringSecurity很多时候宁愿迁出去自己写套Configuer/Filter/Provider…,官方虽提供了很多服务,而且也能看出在尽可能定制化。但背后还是强制耦合引入了太多逻辑,很难与实际业务契合,即便稍有不同在它基础上定制也都需要付出很大代价。这代价不仅指新增代码行数,为了运行稳定 你首先就得彻底清楚它原本引入了哪些逻辑(这点官网文档又做得不好,也很恼火),这就需要大量上手成本。
本文仍存在些许问题,特别是OAuth2AuthorizationCodeGrantFilter
的重定向问题,还有与无状态相悖的oauth2AuthorizedClientRepository
涉及较少,也没一张清晰流程图,时间关系暂且就这样了。即便要用好也得知其然 知其所以然,笔者撰文也只是尽量往上靠,有什么问题还希望指正讨论。
关于 Spring Security 对 OAuth2 认证服务org.springframework.security:spring-security-oauth2-authorization-server
的实现,以及前言提到的 SpringSecurity原理、JWT等等,后面有时间的话也会慢慢更。
以上是脚本宝典为你收集整理的Spring Security OAuth2 完全解析 (流程/原理/实战定制) —— Client / ResourceServer 篇全部内容,希望文章能够帮你解决Spring Security OAuth2 完全解析 (流程/原理/实战定制) —— Client / ResourceServer 篇所遇到的问题。
本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
如您有任何意见或建议可联系处理。小编QQ:384754419,请注明来意。