一、从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. CSPS模拟 86

    看见异或两个字就孩怕 T1 按位? T2 这道异或稍水啊233 貌似可以打表找找规律 emm七种转移,有重复刷表 优化一下? T3 skyh已经接了2杯水了(实际情况他已经ak了) cbx开始抬头傻笑 ...

  2. Spring Boot2 系列教程(二十一)整合 MyBatis

    前面两篇文章和读者聊了 Spring Boot 中最简单的数据持久化方案 JdbcTemplate,JdbcTemplate 虽然简单,但是用的并不多,因为它没有 MyBatis 方便,在 Sprin ...

  3. js简单函数(动态交互)

    <!DOCTYPE html><html>    <head>        <meta charset="UTF-8">      ...

  4. 拎壶冲冲冲专业砸各种培训机构饭碗篇----python自学(一)

    本人一直从事运维工程师,热爱运维,所以从自学的角度站我还是以python运维为主. 一.python自学,当然少不了从hello world开始,话不多说,直接上手练习 1.这个可以学会 print( ...

  5. JS面试题-<变量和类型>-JavaScript浅拷贝与深拷贝

    前言 最开始了解到深浅拷贝是因为准备面试,但那个时候因为在学校做的项目比较少需求也比较简单,所以没有在项目中遇到这类问题,所以对这个问题就属于知道这个知识点,看过相关内容,却没有自己的总结,也没有深入 ...

  6. jquery layui的巨坑

    jquery layui的巨坑 layui 模块不能写在ajax里 因为 layui只能执行一次 第二次会没效果 再执行需要刷新页面再执行

  7. js数组方法大全(下)

    # js数组方法大全(下) 记录一下整理的js数组方法,免得每次要找方法都找不到.图片有点多,注意流量,嘻嘻! 本期分享 forEach() map() filer() every() some() ...

  8. 创建一个线程池(java)

    private ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat("billService-poo ...

  9. 分享使用PHP开发留言板

    首先我不是一名开发人员,只是一名小小的运维工程师,PHP是我自己喜欢的一门开发语言,所以我偶尔也会敲一些代码,写一些案例.今天我给大家分享的是使用PHP开发的留言板,留言板功能不全所以请大家见谅,也不 ...

  10. js 把数组中每个元素的某个字段取出

    方法一:map() let cities = [ {city:"北京",bOn:false}, {city:"上海",bOn:false}, {city:&qu ...