【SpringSecurityOAuth2】源码分析@EnableOAuth2Sso在Spring Security OAuth2 SSO单点登录场景下的作用
一、从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单点登录的原理简单来说就是:标注有@EnableOAuth2Sso的OAuth2 Client应用在通过某种OAuth2授权流程获取访问令牌后(一般是授权码流程),通过访问令牌访问userDetails用户明细这个受保护资源服务,获取用户信息后,将用户信息转换为Spring Security上下文中的认证后凭证Authentication,从而完成标注有@EnableOAuth2Sso的OAuth2 Client应用自身的登录认证的过程。整个过程是基于OAuth2的SSO单点登录SSO流程中需要访问的用户信息资源地址,可以通过
security.oauth2.resource.userInfoUri配置指定最后的通过访问令牌访问受保护资源后,在当前服务创建认证后凭证Authentication(登录态)也可以不通过访问userInfoUri实现,userInfoUri端点是需要用户自己实现。默认情况
security.oauth2.resource.preferTokenInfo=true,获取用户信息使用的是授权服务器的/check_token端点,即TokenInfo,根据访问令牌找到在授权服务器关联的授予这个访问令牌的用户信息Spring Security OAuth2 SSO整个流程实际上是 OAuth2 Client是一个运行在Server上的Webapp的典型场景,很适合使用授权码流程

第二段主要讲了下如何使用@EnableOAuth2Sso:
使用
@EnableOAuth2Sso的OAuth2 Client应用可以使用/login端点用于触发基于OAuth2的SSO流程,这个入口地址也可以通过security.oauth2.sso.login-path来修改如果针对一些安全访问规则有自己的定制,说白了就是自己实现了Spring Security的
WebSecurityConfigurerAdapter想自定义一些安全配置,但又想使用@EnableOAuth2Sso的特性,可以在自己的WebSecurityConfigurerAdapter上使用@EnableOAuth2Sso注解,注解会在你的安全配置基础上做“增强”,至于具体如何“增强”的,后面的源码分析部分会详细解释注意:
如果是在自定义的AutoConfiguration自动配置类上使用
@EnableOAuth2Sso,在第一次重定向到授权服务器时会出现问题,具体是因为通过@EnableOAuth2Client添加的OAuth2ClientContextFilter会被放到springSecurityFilterChain这个Filter后面,导致无法拦截UserRedirectRequiredException需重定向异常如果没有自己的
WebSecurityConfigurerAdapter安全配置,也可以在任意配置类上使用@EnableOAuth2Sso,除了添加OAuth2 SSO的增强外,还会有默认的基本安全配置
二、源码分析@EnableOAuth2Sso作用
首先来看一下@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相关的
OAuth2SsoProperties配置文件 - 导入了3个配置类:
OAuth2SsoDefaultConfiguration、OAuth2SsoCustomConfiguration、ResourceServerTokenServicesConfiguration
@EnableOAuth2Client
@EnableOAuth2Client从名称就可以看出是专门给OAuth2 Client角色使用的注解,其可以独立使用,具体功能需要单独写一篇来分析,大致看一下源码,主要是导入了OAuth2ClientConfiguration配置类
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(OAuth2ClientConfiguration.class)
public @interface EnableOAuth2Client {
}
而OAuth2ClientConfiguration配置类主要做了三件事
- 向Servlet容器添加
OAuth2ClientContextFilter - 创建request scope的Spring Bean:
AccessTokenRequest - 创建session scope的Spring Bean:
OAuth2ClientContext,OAuth2 Client上下文
大体上就是为OAuth2 Client角色创建相关环境
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来增强:SsoSecurityAdapter
*/
@Override
public Object postProcessAfterInitialization(Object bean, String beanName)
throws BeansException {
// 如果是WebSecurityConfigurerAdapter,并且就是添加@EnableOAuth2Sso的那个
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安全配置的情况下,就在用户自定义配置的基础上做OAuth2 SSO的增强,具体分析为
- 首先必须在满足
@Conditional(EnableOAuth2SsoCondition.class)的情况下才可以使用,EnableOAuth2SsoCondition条件指的是@EnableOAuth2Sso注解被使用在WebSecurityConfigurerAdapter上 - 可以看到
OAuth2SsoCustomConfiguration配置类也是一个BeanPostProcessor,其会在Spring初始化Bean的前后做处理,上面代码中会在Sping初始化WebSecurityConfigurerAdapter之后,并且就是添加了@EnableOAuth2Sso注解的WebSecurityConfigurerAdapter之后,为安全配置类做“增强”,添加了一个Advice为SsoSecurityAdapter SsoSecurityAdapter会在用户添加了@EnableOAuth2Sso注解的WebSecurityConfigurerAdapter配置类调用init()初始化方法之前,先添加一段子配置SsoSecurityConfigurer,这个子配置就是实现基于OAuth2 SSO的关键
SsoSecurityConfigurer:OAuth2 SSO核心配置(增强)
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过滤器链添加一个专门用于处理OAuth2 SSO的OAuth2ClientAuthenticationProcessingFilter - 添加处理页面及Ajax请求未认证时的AuthenticationEntryPoint认证入口
OAuth2ClientAuthenticationConfigurer子配置是重点
// 创建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));
// 添加过滤器
builder.addFilterAfter(ssoFilter,
AbstractPreAuthenticatedProcessingFilter.class);
}
}
OAuth2ClientAuthenticationConfigurer子配置将构造好的专门用于处理OAuth2 SSO场景的过滤器OAuth2ClientAuthenticationProcessingFilter添加到springSecurityFilterChain过滤器链中,构造这个Filter时需要
OAuth2RestOperations:专门用于和授权服务器、资源服务器做Rest交互的模板工具类ResourceServerTokenServices:用于访问Token资源服务的类SessionAuthenticationStrategy:OAuth2 SSO认证完成后,使用Spring Security的会话策略
这一步,向springSecurityFilterChain过滤器链中添加OAuth2ClientAuthenticationConfigurer是最核心的一步,整个OAuth2 SSO的交互都由这个Filter完成,OAuth2ClientAuthenticationConfigurer的具体逻辑待后续分析
OAuth2SsoDefaultConfiguration:OAuth2 SSO默认配置
/**
* 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默认配置生效条件
public class OAuth2SsoDefaultConfiguration extends WebSecurityConfigurerAdapter {
private final ApplicationContext applicationContext;
public OAuth2SsoDefaultConfiguration(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
/**
* 1、添加/**都需要认证才能访问的限制
* 2、添加SsoSecurityConfigurer配置
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/**").authorizeRequests().anyRequest().authenticated();
new SsoSecurityConfigurer(this.applicationContext).configure(http);
}
/**
* OAuth2Sso默认配置生效条件
*/
protected static class NeedsWebSecurityCondition extends EnableOAuth2SsoCondition {
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context,
AnnotatedTypeMetadata metadata) {
return ConditionOutcome.inverse(super.getMatchOutcome(context, metadata));
}
}
}
- 条件
NeedsWebSecurityCondition与EnableOAuth2SsoCondition相反,最后满足当用户使用了EnableOAuth2Sso,但其没有被放在自己定义的WebSecurityConfigurerAdapter安全配置类上时,会进入OAuth2 SSO默认配置,从注释信息也可以看出 OAuth2SsoDefaultConfiguration继承了WebSecurityConfigurerAdapter,是一段Spring Security的安全配置- 添加满足
/**路径的请求都需要authenticated()认证,默认安全配置 - 和上面分析一样,使用
SsoSecurityConfigurer子配置,最终会为springSecurityFilterChain过滤器链中添加OAuth2ClientAuthenticationConfigurer
ResourceServerTokenServicesConfiguration:访问Token资源服务的配置
主要作用是创建ResourceServerTokenServices,用于通过访问令牌获取其相关的用户凭据,或者读取访问令牌的完整信息,接口定义如下
public interface ResourceServerTokenServices {
/**
* Load the credentials for the specified access token.
* 加载指定访问令牌的凭据
*
* @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.
* 仅从值中检索完整的访问令牌详细信息
*
* @param accessToken the token value
* @return the full access token with client id etc.
*/
OAuth2AccessToken readAccessToken(String accessToken);
}
具体的ResourceServerTokenServices接口实现分为
- RemoteTokenServices:远端的TokenService
- TokenInfoServices:访问
/check_token端点,根据访问令牌找到在授权服务器关联的授予这个访问令牌的用户信息 - UserInfoTokenServices:访问用户自定义的userInfo端点,根据访问令牌访问受保护资源userInfo
- TokenInfoServices:访问
- JwtTokenServices:基于Json Web Token自包含令牌的TokenService
在通过以上ResourceServerTokenServices接口实现获取用户信息后,就可以在使用@EnableOAuth2Sso注解的OAuth2 Client上创建已认证的用户身份凭证Authentication,完成登录
三、总结
总的来说@EnableOAuth2Sso注解帮助我们快速的将我们的OAuth2 Client应用接入授权服务器完成基于OAuth2的SSO流程,创建登录状态
无论是用户有没有自己的WebSecurityConfigurerAdapter安全配置都可以使用@EnableOAuth2Sso注解,如果有,@EnableOAuth2Sso是在用户的安全配置上做增强
增强的逻辑是在SpringSecurityFilterChain过滤器链上添加OAuth2ClientAuthenticationProcessingFilter这个用于登录认证的Filter,其使用的是OAuth2授权码流程,以下都是这个Filter负责的功能
- 将用户重定向到授权服务器获取授权
- 根据code授权码和OAuth2 clientId、secret获取访问令牌
- 最后使用
ResourceServerTokenServices并携带访问令牌获取用户信息,创建Authentication登录后凭证,完成登录
【SpringSecurityOAuth2】源码分析@EnableOAuth2Sso在Spring Security OAuth2 SSO单点登录场景下的作用的更多相关文章
- Spring Security OAuth2 SSO 单点登录
基于 Spring Security OAuth2 SSO 单点登录系统 SSO简介 单点登录(英语:Single sign-on,缩写为 SSO),又译为单一签入,一种对于许多相互关连,但是又是各自 ...
- Spring Security OAuth2实现单点登录
1.概述 在本教程中,我们将讨论如何使用 Spring Security OAuth 和 Spring Boot 实现 SSO(单点登录). 本示例将使用到三个独立应用 一个授权服务器(中央认证机制) ...
- SpringCloud微服务实战——搭建企业级开发框架(四十):使用Spring Security OAuth2实现单点登录(SSO)系统
一.单点登录SSO介绍 目前每家企业或者平台都存在不止一套系统,由于历史原因每套系统采购于不同厂商,所以系统间都是相互独立的,都有自己的用户鉴权认证体系,当用户进行登录系统时,不得不记住每套系统的 ...
- spring源码分析系列 (3) spring拓展接口InstantiationAwareBeanPostProcessor
更多文章点击--spring源码分析系列 主要分析内容: 一.InstantiationAwareBeanPostProcessor简述与demo示例 二.InstantiationAwareBean ...
- 看源码,重新审视Spring Security中的角色(roles)是怎么回事
在网上看见不少的博客.技术文章,发现大家对于Spring Security中的角色(roles)存在较大的误解,最大的误解就是没有搞清楚其中角色和权限的差别(好多人在学习Spring Security ...
- spring源码分析系列 (5) spring BeanFactoryPostProcessor拓展类PropertyPlaceholderConfigurer、PropertySourcesPlaceholderConfigurer解析
更多文章点击--spring源码分析系列 主要分析内容: 1.拓展类简述: 拓展类使用demo和自定义替换符号 2.继承图UML解析和源码分析 (源码基于spring 5.1.3.RELEASE分析) ...
- spring源码分析系列 (1) spring拓展接口BeanFactoryPostProcessor、BeanDefinitionRegistryPostProcessor
更多文章点击--spring源码分析系列 主要分析内容: 一.BeanFactoryPostProcessor.BeanDefinitionRegistryPostProcessor简述与demo示例 ...
- spring源码分析系列 (2) spring拓展接口BeanPostProcessor
Spring更多分析--spring源码分析系列 主要分析内容: 一.BeanPostProcessor简述与demo示例 二.BeanPostProcessor源码分析:注册时机和触发点 (源码基于 ...
- 精尽MyBatis源码分析 - MyBatis初始化(四)之 SQL 初始化(下)
该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址.Mybatis-Spring 源码分析 GitHub ...
随机推荐
- 学习笔记65_K均值_聚类算法
- Pandas文本数据处理
先初始化数据 import pandas as pd import numpy as np index = pd.Index(data=["Tom", "Bob" ...
- AtCoder Grand Contest 036D - Negative Cycle
神仙题?反正我是完全想不到哇QAQ 这场AGC真的很难咧\(\times 10086\) \(\bf Description\) 一张 \(n\) 个点的图,\(i\) 到 \(i+1\) 有连边. ...
- 通俗易懂了解Vue的计算属性
1.前言 之前在学习vue的过程中,一直没有搞明白计算属性是个怎么回事,以及为什么要有计算属性,使用计算属性有什么好处.今天花时间翻了翻官方文档,才搞清楚其中一二,现将学习心得总结记录如下. 2.为什 ...
- Handler dispatch failed; nested exception is java.lang.NoClassDefFoundError: org/springframework/util/PatternMatchUtils
{ "message": "Handler dispatch failed; nested exception is java.lang.NoClassDefFoundE ...
- Spring源码解析之@Configuration
@Configuration简介 用于标识一个类为配置类,与xml配置效果类似 用法简介 public class TestApplication { public static void main( ...
- PHP Laravel 中使用简单的方法跟踪用户是否在线
今天,我的任务是,在 Laravel 应用程序用户个人资料页面上,用户名旁边添加一个绿点,表示他们是否在线.我首先想到的是,我们将需要启动一个 node.js 服务器并跟踪每个用户的活动套接字连接.然 ...
- PHP 从另一个角度来分析 Laravel 框架的依赖注入功能
从根本上说,依赖注入不是让对象创建一个依赖关系,也不是让工厂对象去创建对象,而是将所需的依赖变成一个外部对象,使之成为一个"某些人的问题” 你为"某些人的问题”注入了类的依赖关系. ...
- PHP 教你使用 Swoole-Tracker 秒级定位 PHP 卡死问题
PHPer 肯定收到过这样的投诉:小菊花一直在转!你们网站怎么这么卡!当我们线上业务遇到这种卡住(阻塞)的情况,大部分 PHPer 会两眼一抹黑,随后想起那句名言:性能瓶颈都在数据库然后把锅甩给DBA ...
- 通过javascript 执行环境理解她
古往今来最难的学的武功(javascript)算其一. 欲练此功必先自宫,愿少侠习的此功,笑傲江湖. 你将了解 执行栈(Execution stack) 执行上下文(Execution Context ...