一、从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个配置类:OAuth2SsoDefaultConfigurationOAuth2SsoCustomConfigurationResourceServerTokenServicesConfiguration

@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));
}
}
}
  • 条件NeedsWebSecurityConditionEnableOAuth2SsoCondition相反,最后满足当用户使用了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
  • 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单点登录场景下的作用的更多相关文章

  1. Spring Security OAuth2 SSO 单点登录

    基于 Spring Security OAuth2 SSO 单点登录系统 SSO简介 单点登录(英语:Single sign-on,缩写为 SSO),又译为单一签入,一种对于许多相互关连,但是又是各自 ...

  2. Spring Security OAuth2实现单点登录

    1.概述 在本教程中,我们将讨论如何使用 Spring Security OAuth 和 Spring Boot 实现 SSO(单点登录). 本示例将使用到三个独立应用 一个授权服务器(中央认证机制) ...

  3. SpringCloud微服务实战——搭建企业级开发框架(四十):使用Spring Security OAuth2实现单点登录(SSO)系统

    一.单点登录SSO介绍   目前每家企业或者平台都存在不止一套系统,由于历史原因每套系统采购于不同厂商,所以系统间都是相互独立的,都有自己的用户鉴权认证体系,当用户进行登录系统时,不得不记住每套系统的 ...

  4. spring源码分析系列 (3) spring拓展接口InstantiationAwareBeanPostProcessor

    更多文章点击--spring源码分析系列 主要分析内容: 一.InstantiationAwareBeanPostProcessor简述与demo示例 二.InstantiationAwareBean ...

  5. 看源码,重新审视Spring Security中的角色(roles)是怎么回事

    在网上看见不少的博客.技术文章,发现大家对于Spring Security中的角色(roles)存在较大的误解,最大的误解就是没有搞清楚其中角色和权限的差别(好多人在学习Spring Security ...

  6. spring源码分析系列 (5) spring BeanFactoryPostProcessor拓展类PropertyPlaceholderConfigurer、PropertySourcesPlaceholderConfigurer解析

    更多文章点击--spring源码分析系列 主要分析内容: 1.拓展类简述: 拓展类使用demo和自定义替换符号 2.继承图UML解析和源码分析 (源码基于spring 5.1.3.RELEASE分析) ...

  7. spring源码分析系列 (1) spring拓展接口BeanFactoryPostProcessor、BeanDefinitionRegistryPostProcessor

    更多文章点击--spring源码分析系列 主要分析内容: 一.BeanFactoryPostProcessor.BeanDefinitionRegistryPostProcessor简述与demo示例 ...

  8. spring源码分析系列 (2) spring拓展接口BeanPostProcessor

    Spring更多分析--spring源码分析系列 主要分析内容: 一.BeanPostProcessor简述与demo示例 二.BeanPostProcessor源码分析:注册时机和触发点 (源码基于 ...

  9. 精尽MyBatis源码分析 - MyBatis初始化(四)之 SQL 初始化(下)

    该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址.Mybatis-Spring 源码分析 GitHub ...

随机推荐

  1. 学习笔记52_mongodb增删改查

    使用use db1作为当前数据库 如果没有db1,会自动创建 使用switch db2,当前数据库切换为db2 使用show dbs,显示当前所有数据库 使用show collection ,显示当前 ...

  2. 一道国外前端面试题引发的Coding...

    刚刚看到CSDN微信公众号一篇文章,关于国外程序员面试前端遇到的一道测试题,有点意思,遂写了下代码,并记录一下~ 题目是这样的: ['Tokyo', 'London', 'Rome', 'Donlon ...

  3. PyQt图形化布局

    安装PyQt第三方库 pip install PyQt5 安装Qt Designer(Qt的布局工具) pip install PyQt5-tools PyChram设置Qt工具 配置Qt Desig ...

  4. windows下离线安装mysql8.0服务(支持多个安装,端口不同就可以)

      1.官网下载 mysql文件.官网下载链接:https://dev.mysql.com/downloads/mysql/ 选择mysql下载的系统版本. 此处可以下载MSI安装包,图简单的朋友可以 ...

  5. 卡特兰数&&prufer序列&&BSGS水题集

    首先说一下BSGS的一个坑点: 解方程A^x≡B(mod p) 需要特判一个东西=>A%p==B%p==0? 如果相等的话puts("1")反之则无解. 因为如果A%p=0, ...

  6. 线段树合并学习笔记(P4556)

    直入主题: 学习线段树合并..... 从名字就能看出,这个东西要合并线段树..... 线段树怎么能合并呢...... 暴力合就行了啊...... 一次从上往下的遍历,把所有的节点信息暴力合并,然后就没 ...

  7. 用OpenGL画线

    . 两点之间的连线称之为线段,在屏幕上显示线段放在现在已经不是稀奇的事情,大多数高级图形API都可以轻松实现,我尝试用OpenGL画线,在这里记录一下收获. . OpenGL这个级别的图形API,通常 ...

  8. 关于一个 websocket 多节点分布式问题的头条面试题

    原文链接,欢迎讨论: [Q023]websocket 服务多节点部署时会有什么问题,怎么解决 你来说说 websocket 有什么用 双向通信,服务器端可以主动 push,给客户端发送通知 那webs ...

  9. JavaScript入门经典(第7版)读书笔记

    断断续续看了十来天,终于看完了,还是学到些东西,这本书还是不错的,各方面都有涉及. 补充了下之前不完善的JS 知识 笔记一般只记必要的东西.‎ Table of Contents 1. JavaScr ...

  10. HTTPS加密流程理解

    HTTPS加密流程 由于HTTP的内容在网络上实际是明文传输,并且也没有身份验证之类的安全措施,所以容易遭到挟持与攻击 HTTPS是通过SSL(安全套接层)和TLS(安全传输协议)的组合使用,加密TC ...