Spring Security 解析(六) —— 基于JWT的单点登陆(SSO)开发及原理解析

JWT实现单点登录(SSO) 相关技术名词: SSOJWT、Redis、AOP、RSA、MD5 - salt 阅读详情

Spring Security 解析(六) —— 基于JWT的单点登陆(SSO)开发及原理解析

在学习Spring Cloud 时,遇到了授权服务oauth 相关内容时,总是一知半解,因此决定先把Spring Security 、Spring Security Oauth2 等权限、认证相关的内容、原理及设计学习并整理一遍。本系列文章就是在学习的过程中加强印象和理解所撰写的,如有侵权请告知。

项目环境:

  • JDK1.8
  • Spring boot 2.x
  • Spring Security 5.x

单点登录(Single Sign On),简称为SSO,是目前比较流行的企业业务整合的解决方案之一。 SSO的定义是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。 单点登陆本质上也是OAuth2的使用,所以其开发依赖于授权认证服务,如果不清楚的可以看我的上一篇文章。

一、 单点登陆 Demo开发

从单点登陆的定义上来看就知道我们需要新建个应用程序,我把它命名为 security-sso-client。接下的开发就在这个应用程序上了。

一、Maven 依赖

主要依赖 spring-boot-starter-security、spring-security-oauth2-autoconfigure、spring-security-oauth2 这3个。其中 spring-security-oauth2-autoconfigure 是Spring Boot 2.X 才有的。

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><<img src="https://github.com/BUG9/spring-security/network/alert/pom.xml/org.springframework.security.oauth:spring-security-oauth2/open--><version>2.3.5.RELEASE</version></dependency>" style="margin: auto" />
二、单点配置 @EnableOAuth2Sso

单点的基础配置引入是依赖 @EnableOAuth2Sso 实现的,在Spring Boot 2.x 及以上版本 的 @EnableOAuth2Sso 是在 spring-security-oauth2-autoconfigure 依赖里的。我这里简单配置了一下:

@Configuration
@EnableOAuth2Sso
public class ClientSecurityConfig extends WebSecurityConfigurerAdapter {@Overridepublic void configure(HttpSecurity http) throws Exception {http.authorizeRequests().antMatchers("/","/error","/login").permitAll().anyRequest().authenticated().and().csrf().disable();}
} 

因为单点期间可能存在某些问题,会重定向到 /error ,所以我们把 /error 设置成无权限访问。

三、测试接口及页面
测试接口
@RestController
@Slf4j
public class TestController {@GetMapping("/client/{clientId}")public String getClient(@PathVariable String clientId) {return clientId;}

} 
测试页面
 <<img src="http://localhost:8091/client/1">跳转到OSS-client-1</a><a href="http://localhost:8092/client/2">跳转到OSS-client-2</a></body></html>" style="margin: auto" />
四、单点配置文件配置授权信息

由于我们要测试多应用间的单点,所以我们至少需要2个单点客户端,我这边通过Spring Boot 的多环境配置实现。

application.yml 配置

我们都知道单点实现本质就是Oauth2的授权码模式,所以我们需要配置访问授权服务器的地址信息,包括 :

  • security.oauth2.client.user-authorization-uri = /oauth/authorize 请求认证的地址,即获取code 码
  • security.oauth2.client.access-token-uri = /oauth/token 请求令牌的地址
  • security.oauth2.resource.jwt.key-uri = /oauth/token_key 解析jwt令牌所需要密钥的地址,服务启动时会调用 授权服务该接口获取jwt key,所以务必保证授权服务正常
  • security.oauth2.client.client-id = client1 clientId 信息
  • security.oauth2.client.client-secret = 123456 clientSecret 信息

其中有几个配置需要简单解释下:

  • security.oauth2.sso.login-path=/login OAuth2授权服务器触发重定向到客户端的路径 ,默认为 /login,这个路径要与授权服务器的回调地址(域名)后的路径一致
  • server.servlet.session.cookie.name = OAUTH2CLIENTSESSION 解决单机开发存在的问题,如果是非单机开发可忽略其配置
auth-server: http://localhost:9090 # authorization服务地址

security:oauth2:client:user-authorization-uri: ${auth-server}/oauth/authorize #请求认证的地址access-token-uri: ${auth-server}/oauth/token #请求令牌的地址resource:jwt:key-uri: ${auth-server}/oauth/token_key #解析jwt令牌所需要密钥的地址,服务启动时会调用 授权服务该接口获取jwt key,所以务必保证授权服务正常sso:login-path: /login #指向登录页面的路径,即OAuth2授权服务器触发重定向到客户端的路径 ,默认为 /login

server:servlet:session:cookie:name: OAUTH2CLIENTSESSION# 解决Possible CSRF detected - state parameter was required but no state could be found问题
spring:profiles:active: client1 
application-client1.yml 配置

application-client2 和 application-client1是一样的,只是端口号和client信息不一样而已,这里就不再重复贴出了。

server:
port: 8091

security:
oauth2:client:client-id: client1client-secret: 123456 
五、单点测试

效果如下:

从效果图中我们可以发现,当我们第一次访问client2 的接口时,跳转到了授权服务的登陆界面,完成登陆后成功跳转回到了client2 的测试接口,并且展示了接口返回值。此时我们访问client1 的 测试接口时直接返回(表面现象)了接口返回值。这就是单点登陆的效果,好奇心强的同学一定会在心里问道:它是如何实现的? 那么接下来我们就来揭开其面纱。

二、 单点登陆原理解析

一、@EnableOAuth2Sso

我们都知道 @EnableOAuth2Sso 是实现单点登陆的最核心配置注解,那么我们来看下 @EnableOAuth2Sso 的源码:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@EnableOAuth2Client
@EnableConfigurationProperties(OAuth2SsoProperties.class)
@Import({ OAuth2SsoDefaultConfiguration.class, OAuth2SsoCustomConfiguration.class,	ResourceServerTokenServicesConfiguration.class })
public @interface EnableOAuth2Sso {

} 

其中我们关注4个配置文件的引用: ResourceServerTokenServicesConfiguration 、OAuth2SsoDefaultConfiguration 、 OAuth2SsoProperties 和 @EnableOAuth2Client:

  • OAuth2SsoDefaultConfiguration 单点登陆的核心配置,内部创建了 SsoSecurityConfigurer 对象, SsoSecurityConfigurer 内部 主要是配置 OAuth2ClientAuthenticationProcessingFilter 这个单点登陆核心过滤器之一。* ResourceServerTokenServicesConfiguration 内部读取了我们在 yml 中配置的信息* OAuth2SsoProperties 配置了回调地址url ,这个就是 security.oauth2.sso.login-path=/login 匹配的* @EnableOAuth2Client 标明单点客户端,其内部 主要 配置了 OAuth2ClientContextFilter 这个单点登陆核心过滤器之一#### 二、 OAuth2ClientContextFilter

OAuth2ClientContextFilter 过滤器类似于 ExceptionTranslationFilter , 它本身没有做任何过滤处理,只要当 chain.doFilter() 出现异常后 做出一个重定向处理。 但别小看这个重定向处理,它可是实现单点登陆的第一步,还记得第一次单点时会跳转到授权服务器的登陆页面么?而这个功能就是 OAuth2ClientContextFilter 实现的。我们来看下其源码:

public void doFilter(ServletRequest servletRequest,
			ServletResponse servletResponse, FilterChain chain)
			throws IOException, ServletException {
		HttpServletRequest request = (HttpServletRequest) servletRequest;
		HttpServletResponse response = (HttpServletResponse) servletResponse;
		request.setAttribute(CURRENT_URI, calculateCurrentUri(request)); // 1、记录当前地址(currentUri)到HttpServletRequest

		try {
			chain.doFilter(servletRequest, servletResponse);
		} catch (IOException ex) {
			throw ex;
		} catch (Exception ex) {
			// Try to extract a SpringSecurityException from the stacktrace
			Throwable[] causeChain = throwableAnalyzer.determineCauseChain(ex);
			UserRedirectRequiredException redirect = (UserRedirectRequiredException) throwableAnalyzer
					.getFirstThrowableOfType(
							UserRedirectRequiredException.class, causeChain);			if (redirect != null) {// 2、判断当前异常 UserRedirectRequiredException 对象 是否为空
				redirectUser(redirect, request, response); // 3、重定向访问 授权服务 /oauth/authorize 
			} else {
				if (ex instanceof ServletException) {
					throw (ServletException) ex;
				}
				if (ex instanceof RuntimeException) {
					throw (RuntimeException) ex;
				}
				throw new NestedServletException("Unhandled exception", ex);
			}
		}
	} 

Debug看下:

整个 filter 分三步:

  • 1、记录当前地址(currentUri)到HttpServletRequest
  • 2、判断当前异常 UserRedirectRequiredException 对象 是否为空
  • 3、重定向访问 授权服务 /oauth/authorize
三、 OAuth2ClientAuthenticationProcessingFilter

OAuth2ClientContextFilter 过滤器 其要完成的工作就是 通过获取到的code码调用 授权服务 /oauth/token 接口获取 token 信息,并将获取到的token 信息解析成 OAuth2Authentication 认证对象。起源如下:

@Override
	public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
			throws AuthenticationException, IOException, ServletException {

		OAuth2AccessToken accessToken;
		try {
			accessToken = restTemplate.getAccessToken(); //1、调用授权服务获取token 
		} catch (OAuth2Exception e) {
			BadCredentialsException bad = new BadCredentialsException("Could not obtain access token", e);
			publish(new OAuth2AuthenticationFailureEvent(bad));
			throw bad; 
		}
		try {
			OAuth2Authentication result = tokenServices.loadAuthentication(accessToken.getValue()); // 2、解析token信息为 OAuth2Authentication 认证对象并返回
			if (authenticationDetailsSource!=null) {
				request.setAttribute(OAuth2AuthenticationDetails.ACCESS_TOKEN_VALUE, accessToken.getValue());
				request.setAttribute(OAuth2AuthenticationDetails.ACCESS_TOKEN_TYPE, accessToken.getTokenType());
				result.setDetails(authenticationDetailsSource.buildDetails(request));
			}
			publish(new AuthenticationSuccessEvent(result));
			return result;
		}
		catch (InvalidTokenException e) {
			BadCredentialsException bad = new BadCredentialsException("Could not obtain user details from token", e);
			publish(new OAuth2AuthenticationFailureEvent(bad));
			throw bad; 
		}

	} 

整个 filter 2点功能:

  • restTemplate.getAccessToken(); //1、 调用授权服务获取token
  • tokenServices.loadAuthentication(accessToken.getValue()); // 2、 解析token信息为 OAuth2Authentication 认证对象并返回

完成上面步骤后就是一个正常的security授权认证过程,这里就不再讲述,有不清楚的同学可以看下我写的相关文章。

四、 AuthorizationCodeAccessTokenProvider

在讲述 OAuth2ClientContextFilter 时有一点没讲,那就是 UserRedirectRequiredException 是 谁抛出来的。 在讲述 OAuth2ClientAuthenticationProcessingFilter 也有一点没讲到,那就是它是如何判断出 当前 /login 是属于 需要获取code码的步骤还是去获取 token 的步骤( 当然是判断/login 是否带有code 参数,这里主要讲明是谁来判断的)。 这2个点都设计到了 AuthorizationCodeAccessTokenProvider 这个类。这个类是何时被调用的? 其实 OAuth2ClientAuthenticationProcessingFilter 隐藏在 restTemplate.getAccessToken(); 这个方法内部 调用的 accessTokenProvider.obtainAccessToken() 这里。 我们来看下OAuth2ClientAuthenticationProcessingFilter 的 obtainAccessToken() 方法内部源码:

public OAuth2AccessToken obtainAccessToken(OAuth2ProtectedResourceDetails details, AccessTokenRequest request)
			throws UserRedirectRequiredException, UserApprovalRequiredException, AccessDeniedException,
			OAuth2AccessDeniedException {

		AuthorizationCodeResourceDetails resource = (AuthorizationCodeResourceDetails) details;

		if (request.getAuthorizationCode() == null) {//1、 判断当前参数是否包含code码 
			if (request.getStateKey() == null) {
				throw getRedirectForAuthorization(resource, request); //2、 不包含则抛出 UserRedirectRequiredException 异常
			}
			obtainAuthorizationCode(resource, request);
		}
		return retrieveToken(request, resource, getParametersForTokenRequest(resource, request),
				getHeadersForTokenRequest(request)); // 3 、 包含则调用获取token 

	} 

整个方法内部分3步:

  • 1、 判断当前参数是否包含code码
  • 2、 不包含则抛出 UserRedirectRequiredException 异常
  • 3、 包含继续获取token

最后可能有同学会问,为什么第一个客户端单点要跳转到授权服务登陆页面去登陆, 而当问第二个客户端却没有,其实 2次 客户端单点的流程都是一样的,都是授权码模式,但为什么客户端2 却不需要登陆呢? 其实是因为Cookies/Session的原因,因为我们访问同2个客户端基本上都是在同一个浏览器中进行的。 不信的同学可以试试2个浏览器分别访问2个单点客户端。

三、 个人总结

单点登陆本质上就是授权码模式,所以理解起来还是很容易的,如果非要给个流程图,还是那张授权码流程图
   本文介绍 基于JWT的单点登陆(SSO)开发及原理解析 开发的代码可以访问代码仓库 ,项目的github 地址 : github.com/BUG9/spring…

如果您对这些感兴趣,欢迎star、follow、收藏、转发给予支持!

微服务6——SSO单点登陆系统(SSO) 什么是单点登录系统 即多个站点共用一台认证授权服务器,用户在其中任何一个站点登录后,可以免登录访问其他所有站点。 思路 用户登录成功后,会基于JWT技术生成一个token(令牌),用户信息可以存储到这个token中.然后写到客户端进行存储,后续用户在访问资源时,对token内容解析,检查登录状态以及权限信息,无须再访问数据库. 步骤 1、创建父工程;删除父工程src目录;初始化pom文件内容(见微服务1) 2、导入数据库文件 source d:/jt-sso... 阅读详情

相关推荐

【一键搞定】ThinkPHP6.0.2+PHPStudy+Composer极速安装指南——附完整下载包与避坑技巧

本文提供了一份ThinkPHP6.0.2的完整下载包,并详细介绍了如何配合PHPStudy和Composer进行极速安装与配置。指南涵盖了环境准备、Composer国内镜像加速、项目部署及常见避坑技巧,旨在帮助开发者,尤其是内网或网络不佳的用户,快速搭建可运行的TP6开发环境,绕过依赖下载的繁琐步骤。

assembly8low的博客 987

关于Spring Security框架 关于单点登录sso

Security

weixin_73849581的博客 1762

Spring Boot进阶(87):基于Spring Security实现单点登录(SSO) | 超级详细,建议收藏

基于Spring Security实现单点登录(SSO),一文带你学会。

**My Coding Family** 3865

Spring Security实现单点登录SSO(一)

本章主要阐述Spring Security实现单点登录的流程,登录方式为用户名密码登录。后续章节会继续拓展单点登录的其他内容。由于个人知识有限,欢迎各位大佬指正不当之处。

m0_47533296的博客 2668

实战~ 基于Security+JWT单点登陆开发原理解析

在学习 Spring Cloud 时,遇到了授权服务 oauth 相关内容时,总是一知半解,因此决定先把 Spring SecuritySpring Security Oauth2 等权限、认证相关的内容、原理及设计学习并整理一遍 基于 JWT单点登陆(SSO) 开发原理解析   在学习 Spring Cloud 时,遇到了授权服务 oauth 相关内容时,总是一知半解,因此决定先把 Spring SecuritySpring Security Oauth2 等权限、认证相关的内容、原理

SharingOfficer的博客 693

实战!基于Security+JWT单点登陆开发原理解析

作者:BUG9原文:https://segmentfault.com/a/1190000020398550在学习 Spring Cloud 时,遇到了授权服务 oauth 相关内容时,总是...

MarkerHub的博客 561

单点登录SSO解决方案之SpringSecurity+JWT实现

  通过前面几天文章我们详细的介绍了SpringSecurity的使用,本文我们来看下,结合JWT来实现单点登录操作。 一、什么是单点登陆   单点登录(Single Sign On),简称为 SSO,是目前比较流行的企业业务整合的解决方案之一。SSO的定义是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统 二、简单的运行机制   单点登录的机制其实是比较简单的,用一个现实中的...

波波烤鸭的博客 2万+

实战基于Security+JWT单点登陆开发原理解析

点击“终码一生”,关注,置顶公众号 每日技术干货,第一时间送达! 在学习 Spring Cloud 时,遇到了授权服务 oauth 相关内容时,总是一知半解,因此决定先把 Spring SecuritySpring Security Oauth2 等权限、认证相关的内容、原理及设计学习并整理一遍。 Spring Security 解析 () —— 基于 JWT单点登陆(SSO) 开发原理解析 在学习 Spring Cloud 时,遇到了授权服务 oauth 相关内容时,总是一知半解

终码一生 193

微服务版单点登陆系统(SSO)实践

单点登陆系统简介 背景分析 传统的登录系统中,每个站点都实现了自己的专用登录模块。各站点的登录状态相互不认可,各站点需要逐一手工登录。例如: 这样的系统,我们又称之为多点登陆系统。应用起来相对繁琐(每次访问资源服务都需要重新登陆认证和授权)。与此同时,系统代码的重复也比较高。由此单点登陆系统诞生。 单点登陆系统概述 单点登录,英文是 Single Sign On(缩写为 SSO)。即多个站点共用一台认证授权服务器,用户在其中任何一个站点登录后,可以免登录访问其他所有站点。而且,各站点间可以通.

p2060550880的博客 4461

SpringBoot+SpringSecurity+JWT整合实现单点登录SSO史上最全详解

你知道的越多,不知道的就越多,业余的像一棵小草!你来,我们一起精进!你不来,我和你的竞争对手一起精进!编辑:业余草blog.csdn.net/qq_38526573/article/de...

xmt1139057136的专栏 5505

Spring Security + JWT 实现单点登录

作者 |波波烤鸭来源 |https://dpb-bobokaoya-sm.blog.csdn.net/article/details/103409430本文我们来看下 SpringSe...

程序猿DD 1113

SpringBoot+JWT整合实现单点登录SSO

spring boot单点登录

qq_24692021的博客 1566

YYC松鼠聚合直播系统融和电商商城、网红热点、娱乐竞技直播等 v3.0.2.zip

松鼠聚合直播系统自2019年开发,松鼠聚合直播系统是一套团队自主研发、源码开源,可自由二次开发的直播系统。系统融和电商商城、网红热点、娱乐竞技直播等,能够快速实现吸粉引流,集合在线直播、互动分享、社交传播等一体化功能,实现“直播+”的效果最大化运营需求。关于接口支持定制直播APP,直播网站的接口,详情可联系客服近期更新日志2022年10月28日松鼠聚合直播系统v3.0.2更新更新日志·修复上传视频路径不正确导致前台视频模块不正常2022年10月25日松鼠聚合直播系统v3.0.2更新更新日志·修复第三方库导致后台打包后网络错误2022年5月13日松鼠聚合直播系统进行v3更新,本次为v3.0.1更新更新日志·加密通信·优化注册登录机制·其他筹备工作2020年4月更新松鼠聚合直播系统2.0版本1、支持定制各类直播接口数据,大型游戏,体育,娱乐直播等需要得到其官方授权许可,非法改动接口后果一切自负 。2、支持短视频发布,短视频观看支持全屏。3、支持直播内页广告位,banner广告位,文字广告。4、支持点卡系统,后台可配

上一篇: Spring Security 多过滤链的使用
下一篇: Spring Security 快速入门
程序员负总裁
博客等级 码龄4年 1万+粉丝 2074原创
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值