一、從Spring Security OAuth2官方文檔了解@EnableOAuth2Sso作用
spring-security-oauth2-boot 2.2.0.RELEASE Single Sign On文檔地址
先從第一段介紹開始会烙,加上自己的分析:
@EnableOAuth2Sso
是使用在OAuth2 Client角色上的注解负懦,從其包路徑也可以看出org.springframework.boot.autoconfigure.security.oauth2.client@EnableOAuth2Sso
單點(diǎn)登錄的原理簡(jiǎn)單來(lái)說(shuō)就是:標(biāo)注有@EnableOAuth2Sso
的OAuth2 Client應(yīng)用在通過(guò)某種OAuth2授權(quán)流程獲取訪問(wèn)令牌后(一般是授權(quán)碼流程),通過(guò)訪問(wèn)令牌訪問(wèn)userDetails用戶明細(xì)這個(gè)受保護(hù)資源服務(wù)持搜,獲取用戶信息后密似,將用戶信息轉(zhuǎn)換為Spring Security上下文中的認(rèn)證后憑證Authentication,從而完成標(biāo)注有@EnableOAuth2Sso
的OAuth2 Client應(yīng)用自身的登錄認(rèn)證的過(guò)程葫盼。整個(gè)過(guò)程是基于OAuth2的SSO單點(diǎn)登錄SSO流程中需要訪問(wèn)的用戶信息資源地址残腌,可以通過(guò)
security.oauth2.resource.userInfoUri
配置指定最后的通過(guò)訪問(wèn)令牌訪問(wèn)受保護(hù)資源后,在當(dāng)前服務(wù)創(chuàng)建認(rèn)證后憑證Authentication(登錄態(tài))也可以不通過(guò)訪問(wèn)userInfoUri實(shí)現(xiàn)贫导,userInfoUri端點(diǎn)是需要用戶自己實(shí)現(xiàn)抛猫。默認(rèn)情況
security.oauth2.resource.preferTokenInfo=true
,獲取用戶信息使用的是授權(quán)服務(wù)器的/check_token
端點(diǎn)孩灯,即TokenInfo闺金,根據(jù)訪問(wèn)令牌找到在授權(quán)服務(wù)器關(guān)聯(lián)的授予這個(gè)訪問(wèn)令牌的用戶信息Spring Security OAuth2 SSO整個(gè)流程實(shí)際上是 OAuth2 Client是一個(gè)運(yùn)行在Server上的Webapp的典型場(chǎng)景,很適合使用授權(quán)碼流程
第二段主要講了下如何使用@EnableOAuth2Sso
:
使用
@EnableOAuth2Sso
的OAuth2 Client應(yīng)用可以使用/login
端點(diǎn)用于觸發(fā)基于OAuth2的SSO流程峰档,這個(gè)入口地址也可以通過(guò)security.oauth2.sso.login-path
來(lái)修改-
如果針對(duì)一些安全訪問(wèn)規(guī)則有自己的定制败匹,說(shuō)白了就是自己實(shí)現(xiàn)了Spring Security的
WebSecurityConfigurerAdapter
想自定義一些安全配置寨昙,但又想使用@EnableOAuth2Sso
的特性,可以在自己的WebSecurityConfigurerAdapter
上使用@EnableOAuth2Sso
注解掀亩,注解會(huì)在你的安全配置基礎(chǔ)上做“增強(qiáng)”舔哪,至于具體如何“增強(qiáng)”的,后面的源碼分析部分會(huì)詳細(xì)解釋注意:
如果是在自定義的AutoConfiguration自動(dòng)配置類上使用
@EnableOAuth2Sso
槽棍,在第一次重定向到授權(quán)服務(wù)器時(shí)會(huì)出現(xiàn)問(wèn)題捉蚤,具體是因?yàn)橥ㄟ^(guò)@EnableOAuth2Client
添加的OAuth2ClientContextFilter
會(huì)被放到springSecurityFilterChain
這個(gè)Filter后面,導(dǎo)致無(wú)法攔截UserRedirectRequiredException
需重定向異常 如果沒(méi)有自己的
WebSecurityConfigurerAdapter
安全配置炼七,也可以在任意配置類上使用@EnableOAuth2Sso
缆巧,除了添加OAuth2 SSO的增強(qiáng)外,還會(huì)有默認(rèn)的基本安全配置
二豌拙、源碼分析@EnableOAuth2Sso作用
首先來(lái)看一下@EnableOAuth2Sso
的源碼
/**
* Enable OAuth2 Single Sign On (SSO). If there is an existing
* {@link WebSecurityConfigurerAdapter} provided by the user and annotated with
* {@code @EnableOAuth2Sso}, it is enhanced by adding an authentication filter and an
* authentication entry point. If the user only has {@code @EnableOAuth2Sso} but not on a
* WebSecurityConfigurerAdapter then one is added with all paths secured.
*
* @author Dave Syer
* @since 1.3.0
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@EnableOAuth2Client
@EnableConfigurationProperties(OAuth2SsoProperties.class)
@Import({ OAuth2SsoDefaultConfiguration.class, OAuth2SsoCustomConfiguration.class,
ResourceServerTokenServicesConfiguration.class })
public @interface EnableOAuth2Sso {
}
可以看到主要做了幾件事
- 添加
@EnableOAuth2Client
- 啟用OAuth2 SSO相關(guān)的
OAuth2SsoProperties
配置文件 - 導(dǎo)入了3個(gè)配置類:
OAuth2SsoDefaultConfiguration
陕悬、OAuth2SsoCustomConfiguration
、ResourceServerTokenServicesConfiguration
@EnableOAuth2Client
@EnableOAuth2Client
從名稱就可以看出是專門給OAuth2 Client角色使用的注解按傅,其可以獨(dú)立使用墩莫,具體功能需要單獨(dú)寫一篇來(lái)分析,大致看一下源碼逞敷,主要是導(dǎo)入了OAuth2ClientConfiguration
配置類
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(OAuth2ClientConfiguration.class)
public @interface EnableOAuth2Client {
}
而OAuth2ClientConfiguration
配置類主要做了三件事
- 向Servlet容器添加
OAuth2ClientContextFilter
- 創(chuàng)建request scope的Spring Bean:
AccessTokenRequest
- 創(chuàng)建session scope的Spring Bean:
OAuth2ClientContext
狂秦,OAuth2 Client上下文
大體上就是為OAuth2 Client角色創(chuàng)建相關(guān)環(huán)境
OAuth2SsoCustomConfiguration:OAuth2 SSO自定義配置
/**
* Configuration for OAuth2 Single Sign On (SSO) when there is an existing
* {@link WebSecurityConfigurerAdapter} provided by the user and annotated with
* {@code @EnableOAuth2Sso}. The user-provided configuration is enhanced by adding an
* authentication filter and an authentication entry point.
*
* @author Dave Syer
*/
@Configuration
@Conditional(EnableOAuth2SsoCondition.class) //OAuth2 SSO自定義配置生效條件
public class OAuth2SsoCustomConfiguration
implements ImportAware, BeanPostProcessor, ApplicationContextAware {
private Class<?> configType;
private ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
@Override
public void setImportMetadata(AnnotationMetadata importMetadata) {
this.configType = ClassUtils.resolveClassName(importMetadata.getClassName(),
null);
}
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException {
return bean;
}
/**
* BeanPostProcessor的初始化后方法
* 給用戶自定義的WebSecurityConfigurerAdapter添加Advice來(lái)增強(qiáng):SsoSecurityAdapter
*/
@Override
public Object postProcessAfterInitialization(Object bean, String beanName)
throws BeansException {
// 如果是WebSecurityConfigurerAdapter,并且就是添加@EnableOAuth2Sso的那個(gè)
if (this.configType.isAssignableFrom(bean.getClass())
&& bean instanceof WebSecurityConfigurerAdapter) {
ProxyFactory factory = new ProxyFactory();
factory.setTarget(bean);
factory.addAdvice(new SsoSecurityAdapter(this.applicationContext));
bean = factory.getProxy();
}
return bean;
}
/**
* 攔截用戶的WebSecurityConfigurerAdapter
* 在其init()初始化之前推捐,添加SsoSecurityConfigurer配置
*/
private static class SsoSecurityAdapter implements MethodInterceptor {
private SsoSecurityConfigurer configurer;
SsoSecurityAdapter(ApplicationContext applicationContext) {
this.configurer = new SsoSecurityConfigurer(applicationContext);
}
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
if (invocation.getMethod().getName().equals("init")) {
Method method = ReflectionUtils
.findMethod(WebSecurityConfigurerAdapter.class, "getHttp");
ReflectionUtils.makeAccessible(method);
HttpSecurity http = (HttpSecurity) ReflectionUtils.invokeMethod(method,
invocation.getThis());
this.configurer.configure(http);
}
return invocation.proceed();
}
}
}
OAuth2SsoCustomConfiguration
自定義配置指的是如果用戶有自定義的WebSecurityConfigurerAdapter
安全配置的情況下裂问,就在用戶自定義配置的基礎(chǔ)上做OAuth2 SSO的增強(qiáng),具體分析為
- 首先必須在滿足
@Conditional(EnableOAuth2SsoCondition.class)
的情況下才可以使用牛柒,EnableOAuth2SsoCondition
條件指的是@EnableOAuth2Sso
注解被使用在WebSecurityConfigurerAdapter
上 - 可以看到
OAuth2SsoCustomConfiguration
配置類也是一個(gè)BeanPostProcessor
堪簿,其會(huì)在Spring初始化Bean的前后做處理,上面代碼中會(huì)在Sping初始化WebSecurityConfigurerAdapter
之后皮壁,并且就是添加了@EnableOAuth2Sso
注解的WebSecurityConfigurerAdapter
之后椭更,為安全配置類做“增強(qiáng)”,添加了一個(gè)Advice為SsoSecurityAdapter
-
SsoSecurityAdapter
會(huì)在用戶添加了@EnableOAuth2Sso
注解的WebSecurityConfigurerAdapter
配置類調(diào)用init()
初始化方法之前蛾魄,先添加一段子配置SsoSecurityConfigurer
虑瀑,這個(gè)子配置就是實(shí)現(xiàn)基于OAuth2 SSO的關(guān)鍵
SsoSecurityConfigurer:OAuth2 SSO核心配置(增強(qiáng))
class SsoSecurityConfigurer {
public void configure(HttpSecurity http) throws Exception {
OAuth2SsoProperties sso = this.applicationContext
.getBean(OAuth2SsoProperties.class);
// Delay the processing of the filter until we know the
// SessionAuthenticationStrategy is available:
http.apply(new OAuth2ClientAuthenticationConfigurer(oauth2SsoFilter(sso)));
addAuthenticationEntryPoint(http, sso);
}
- 添加
OAuth2ClientAuthenticationConfigurer
子配置,為了向springSecurityFilterChain過(guò)濾器鏈添加一個(gè)專門用于處理OAuth2 SSO的OAuth2ClientAuthenticationProcessingFilter
- 添加處理頁(yè)面及Ajax請(qǐng)求未認(rèn)證時(shí)的AuthenticationEntryPoint認(rèn)證入口
OAuth2ClientAuthenticationConfigurer
子配置是重點(diǎn)
// 創(chuàng)建OAuth2ClientAuthenticationProcessingFilter
private OAuth2ClientAuthenticationProcessingFilter oauth2SsoFilter(
OAuth2SsoProperties sso) {
OAuth2RestOperations restTemplate = this.applicationContext
.getBean(UserInfoRestTemplateFactory.class).getUserInfoRestTemplate();
ResourceServerTokenServices tokenServices = this.applicationContext
.getBean(ResourceServerTokenServices.class);
OAuth2ClientAuthenticationProcessingFilter filter = new OAuth2ClientAuthenticationProcessingFilter(
sso.getLoginPath());
filter.setRestTemplate(restTemplate);
filter.setTokenServices(tokenServices);
filter.setApplicationEventPublisher(this.applicationContext);
return filter;
}
// OAuth2ClientAuthenticationConfigurer子配置
private static class OAuth2ClientAuthenticationConfigurer
extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
private OAuth2ClientAuthenticationProcessingFilter filter;
OAuth2ClientAuthenticationConfigurer(
OAuth2ClientAuthenticationProcessingFilter filter) {
this.filter = filter;
}
@Override
public void configure(HttpSecurity builder) throws Exception {
OAuth2ClientAuthenticationProcessingFilter ssoFilter = this.filter;
ssoFilter.setSessionAuthenticationStrategy(
builder.getSharedObject(SessionAuthenticationStrategy.class));
// 添加過(guò)濾器
builder.addFilterAfter(ssoFilter,
AbstractPreAuthenticatedProcessingFilter.class);
}
}
OAuth2ClientAuthenticationConfigurer
子配置將構(gòu)造好的專門用于處理OAuth2 SSO場(chǎng)景的過(guò)濾器OAuth2ClientAuthenticationProcessingFilter
添加到springSecurityFilterChain過(guò)濾器鏈中滴须,構(gòu)造這個(gè)Filter時(shí)需要
-
OAuth2RestOperations
:專門用于和授權(quán)服務(wù)器舌狗、資源服務(wù)器做Rest交互的模板工具類 -
ResourceServerTokenServices
:用于訪問(wèn)Token資源服務(wù)的類 -
SessionAuthenticationStrategy
:OAuth2 SSO認(rèn)證完成后,使用Spring Security的會(huì)話策略
這一步扔水,向springSecurityFilterChain過(guò)濾器鏈中添加OAuth2ClientAuthenticationConfigurer
是最核心的一步痛侍,整個(gè)OAuth2 SSO的交互都由這個(gè)Filter完成,OAuth2ClientAuthenticationConfigurer
的具體邏輯待后續(xù)分析
OAuth2SsoDefaultConfiguration:OAuth2 SSO默認(rèn)配置
/**
* Configuration for OAuth2 Single Sign On (SSO). If the user only has
* {@code @EnableOAuth2Sso} but not on a {@code WebSecurityConfigurerAdapter} then one is
* added with all paths secured.
*
* @author Dave Syer
* @since 1.3.0
*/
@Configuration
@Conditional(NeedsWebSecurityCondition.class) //OAuth2Sso默認(rèn)配置生效條件
public class OAuth2SsoDefaultConfiguration extends WebSecurityConfigurerAdapter {
private final ApplicationContext applicationContext;
public OAuth2SsoDefaultConfiguration(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
/**
* 1魔市、添加/**都需要認(rèn)證才能訪問(wèn)的限制
* 2主届、添加SsoSecurityConfigurer配置
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/**").authorizeRequests().anyRequest().authenticated();
new SsoSecurityConfigurer(this.applicationContext).configure(http);
}
/**
* OAuth2Sso默認(rèn)配置生效條件
*/
protected static class NeedsWebSecurityCondition extends EnableOAuth2SsoCondition {
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context,
AnnotatedTypeMetadata metadata) {
return ConditionOutcome.inverse(super.getMatchOutcome(context, metadata));
}
}
}
- 條件
NeedsWebSecurityCondition
與EnableOAuth2SsoCondition
相反赵哲,最后滿足當(dāng)用戶使用了EnableOAuth2Sso
,但其沒(méi)有被放在自己定義的WebSecurityConfigurerAdapter
安全配置類上時(shí)君丁,會(huì)進(jìn)入OAuth2 SSO默認(rèn)配置誓竿,從注釋信息也可以看出 -
OAuth2SsoDefaultConfiguration
繼承了WebSecurityConfigurerAdapter
,是一段Spring Security的安全配置 - 添加滿足
/**
路徑的請(qǐng)求都需要authenticated()
認(rèn)證谈截,默認(rèn)安全配置 - 和上面分析一樣,使用
SsoSecurityConfigurer
子配置涧偷,最終會(huì)為springSecurityFilterChain過(guò)濾器鏈中添加OAuth2ClientAuthenticationConfigurer
ResourceServerTokenServicesConfiguration:訪問(wèn)Token資源服務(wù)的配置
主要作用是創(chuàng)建ResourceServerTokenServices
簸喂,用于通過(guò)訪問(wèn)令牌獲取其相關(guān)的用戶憑據(jù),或者讀取訪問(wèn)令牌的完整信息燎潮,接口定義如下
public interface ResourceServerTokenServices {
/**
* Load the credentials for the specified access token.
* 加載指定訪問(wèn)令牌的憑據(jù)
*
* @param accessToken The access token value.
* @return The authentication for the access token.
* @throws AuthenticationException If the access token is expired
* @throws InvalidTokenException if the token isn't valid
*/
OAuth2Authentication loadAuthentication(String accessToken) throws AuthenticationException, InvalidTokenException;
/**
* Retrieve the full access token details from just the value.
* 僅從值中檢索完整的訪問(wèn)令牌詳細(xì)信息
*
* @param accessToken the token value
* @return the full access token with client id etc.
*/
OAuth2AccessToken readAccessToken(String accessToken);
}
具體的ResourceServerTokenServices
接口實(shí)現(xiàn)分為
-
RemoteTokenServices:遠(yuǎn)端的TokenService
-
TokenInfoServices:訪問(wèn)
/check_token
端點(diǎn)喻鳄,根據(jù)訪問(wèn)令牌找到在授權(quán)服務(wù)器關(guān)聯(lián)的授予這個(gè)訪問(wèn)令牌的用戶信息 - UserInfoTokenServices:訪問(wèn)用戶自定義的userInfo端點(diǎn),根據(jù)訪問(wèn)令牌訪問(wèn)受保護(hù)資源userInfo
-
TokenInfoServices:訪問(wèn)
- JwtTokenServices:基于Json Web Token自包含令牌的TokenService
在通過(guò)以上ResourceServerTokenServices
接口實(shí)現(xiàn)獲取用戶信息后确封,就可以在使用@EnableOAuth2Sso
注解的OAuth2 Client上創(chuàng)建已認(rèn)證的用戶身份憑證Authentication除呵,完成登錄
三、總結(jié)
總的來(lái)說(shuō)@EnableOAuth2Sso
注解幫助我們快速的將我們的OAuth2 Client應(yīng)用接入授權(quán)服務(wù)器完成基于OAuth2的SSO流程爪喘,創(chuàng)建登錄狀態(tài)
無(wú)論是用戶有沒(méi)有自己的WebSecurityConfigurerAdapter
安全配置都可以使用@EnableOAuth2Sso
注解颜曾,如果有,@EnableOAuth2Sso
是在用戶的安全配置上做增強(qiáng)
增強(qiáng)的邏輯是在SpringSecurityFilterChain過(guò)濾器鏈上添加OAuth2ClientAuthenticationProcessingFilter
這個(gè)用于登錄認(rèn)證的Filter秉剑,其使用的是OAuth2授權(quán)碼流程泛豪,以下都是這個(gè)Filter負(fù)責(zé)的功能
- 將用戶重定向到授權(quán)服務(wù)器獲取授權(quán)
- 根據(jù)code授權(quán)碼和OAuth2 clientId、secret獲取訪問(wèn)令牌
- 最后使用
ResourceServerTokenServices
并攜帶訪問(wèn)令牌獲取用戶信息侦鹏,創(chuàng)建Authentication登錄后憑證诡曙,完成登錄