spring boot如何基于JWT实现单点登录详解

前言

最近我们组要给负责的一个管理系统 A 集成另外一个系统 B,为了让用户使用更加便捷,避免多个系统重复登录,希望能够达到这样的效果——用户只需登录一次就能够在这两个系统中进行操作。很明显这就是单点登录(Single Sign-On)达到的效果,正好可以明目张胆的学一波单点登录知识。

本篇主要内容如下:

  • SSO 介绍
  • SSO 的几种实现方式对比
  • 基于 JWT 的 spring boot 单点登录实战

注意:

SSO 这个概念已经出现很久很久了,目前各种平台都有非常成熟的实现,比如OpenSSO,OpenAM,Kerberos,CAS等,当然很多时候成熟意味着复杂。本文不讨论那些成熟方案的使用,也不考虑 SSO 在 CS 应用中的使用。

什么是 SSO

单点点说就是:一次登录后可免登陆访问其他的可信平台。比如我们登录淘宝网后,再打开天猫首页可以发现已经是登录状态了。SSO 是一种比较流行的服务于企业业务整合的一种解决方案。

如何实现 SSO

我们都知道目前的 http 协议是无状态的,也就是第一次请求和第二次请求是完全独立,不相关的,但现实中我们的业务逻辑都是有状态的,这样就引入了 cookie-session 的机制来维护状态,浏览器端存储一个 sessionId,后台存储跟该 sessionId 相关的数据。每次向后台发起请求时都携带此 sessionId 就能维持状态了。然后就有了 cookie,浏览器在发送请求时自动将 cookie 中的数据放到请求中,发给服务端,无需手动设置。

然后我们可以考虑考虑实现 SSO 的核心是什么?答案就是如何让一个平台 A 登录后,其他的平台也能获取到平台 A 的登录信息(在 cookie-session 机制中就是 sessionId)。

方案一 共享 cookie

基于 cookie-session 机制的系统中,登录系统后会返回一个 sessionId 存储在 cookie 中,如果我们能够让另外一个系统也能获取到这个 cookie,不就获取到凭证信息了,无需再次登录。刚好浏览器的 cookie 可以实现这样的效果(详见web 跨域及 cookie 学习)。

cookie 允许同域名(或者父子域名)的不同端口中共享 cookie,这点和 http 的同域策略不一样(http 请求只要协议、域名、端口不完全相同便认为跨域)。因此只需将多个应用前台页面部署到相同的域名(或者父子域名),然后共享 session 便能够实现单点登录。架构如下:

上面方案显而易见的限制就是不仅前台页面需要共享 cookie,后台也需要共享 session(可以用jwt来干掉 session,但是又会引入新的问题,这里不展开).这个方案太简单了,不作进一步说明。

方案二 基于回调实现

通过上文可以知道,要实现单点登录只需将用户的身份凭证共享给各个系统,让后台知道现在是谁在访问。就能实现一次登录,到处访问的效果,实在是非常方便的。在 session 机制中是共享 sessionId,然后多个后台使用同一个 session 源即可。这里我们用一种新的基于 JWT 的 token 方式来实现,不了解 JWT 的可以看这篇:java-jwt 生成与校验,简单来说 jwt 可以携带无法篡改的信息(一段篡改就会校验失败),所以我们可以将用户 id 等非敏感信息直接放到 jwt 中,干掉了后台的 session。然后我们要做的就是将 jwt 共享给各个平台页面即可。系统架构如下:

此架构中,业务系统 A 和业务系统 B 之间不需要有任何联系,他们都只和 SSO 认证平台打交道,因此可以任意部署,没有同域的限制。你可能就要问了这样要怎么共享身份凭证(也就是 jwt 字符串)?这里就要通过 url 参数来进行骚操作了。

文字总结来说是这样的:jwt 存到认证平台前端的 localStore(不一定是 localStore,cookie,sessionStore 都可以),然后业务平台携带自己的回调地址跳转到认证中心的前台,认证中心的前台再将 ujwt 作为 url 参数,跳回到那个回调地址上,这样就完成了 jwt 的共享。

文字很可能看不懂,下面是整个过程的路程图:

相信通过上面的流程图你应该能大概看明白,jwt 是如何共享了的吧,还看不懂的继续看下来,下面上一个 spring boot 实现的简易 SSO 认证。主要有两个系统:SSO 认证中心,系统 A(系统 A 换不同端口运行就是系统 A、B、C、D 了).

实战

实现 SSO 认证中心

spring boot 框架先搭起来,由于是简易项目,除 spring boot web 基本依赖,只需要如下的额外依赖:

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.4</version>
</dependency>
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.7.0</version>
</dependency>

完整的 POM 文件,请到 github 上查看.

后台实现

后台做的事情并不多,只有以下 5 个方法:

  • /login : 登录成功后签发一个 jwt token

在 demo 中只是简单对比用户名密码如果是一样的认为登录成功,返回 token

  • /checkJwt : 检查 jwt 的有效性

检查传来的 jwt-token 是否有效,返回失效的 jwt 列表

  • /refreshjwt : 刷新 jwt

判断该 jwt 是否快要过期,如果快要过期,生成一个新的 jwt 返回

  • /inValid : 让某个 jwt 失效

jwt 如何失效一直是一个比较麻烦的问题,各有利弊。本例中采用的是为每个 jwt 生成一个随机的秘钥 secret,将 jwt–secret 保存到 redis 中,想要让某个 jwt 失效,只需将该记录在 redis 中删除即可(这样在解密时便无法获取到 secret)。但是这样让无状态的认证机制变成有状态了(记录了 jwt 和 secret 的对应关系)。

总结来说 SSO 后台主要只做了两件事:验证用户名密码返回 jwt;验证 jwt 是否合法。具体代码查看 github 上 sso 目录下的代码。

 前台实现

前台的逻辑较为复杂,不是那么容易理解,不明白的多看几遍上面的流程图。

再次回到 SSO 的重点:分享登录状态。要如何在前台将登录状态(在这里就是 jwt 字符串)分享出去呢?由于浏览器的限制,除了 cookie 外没有直接共享数据的办法。既然没有直接共享,那肯定是有间接的办法的!

这个办法就是回调。系统 A 的前台在跳转到 SSO 的前台时,将当前路径作为 url 参数传递给 sso 前台,sso 前台在获取到 jwt 后,再跳转到系统 A 传过来的 url 路径上,并带上 jwt 作为 url 参数。这就完成了 jwt 的一次共享,从 sso 共享到系统 A。

打个比方:你点了个外卖,别人要怎么把外卖给你呢?显然你会留下的地址,让别人带上饭送到这个地址,然后你就能享用美食了。这和 jwt 的传递非常相识了。

系统 A 说:我要 jwt,快把它送到http://localhost:8081/test1/这个地址上。

SSO 说:好嘞,这个地址是合法的可以送 jwt 过去,这就跳转过去:http://localhost:8081/test1/?jwt=abcdefj.asdf.asdfasf

系统 A 说:不错不错,真香。

要注意这里有个坑就是:如果另外一个恶意系统 C 安装相同的格式跳转到 SSO,想要获取 jwt,这显然是不应该给它的。所以在回跳回去的时候要判断一下这个回调地址是不是合法的,能不能给 jwt 给它,可以向后台请求判断也可以在 sso 前台直接写死合法的地址。在 demo 是没有这个判断过程的。

实现业务系统

业务系统代码非常简单,主要是用了一个拦截器,拦截 http 请求,提取出 token 向 sso 认证中心验证 token 是否有效,有效放行,否则返回错误给前端。太简单也不贴代码了,到 github 上看看就明白了。

效果

上面说了一大串都是原理了,其实这个难也就难在原理部分,代码实现并没有那么复杂。这里就不贴代码了,有需要直接到 github 上看。

这里上几个效果图:

系统 A 首次登陆系统

可以看到首次登陆是需要跳到  sso 认证中心输入用户名密码进行登陆验证的。登陆成功回跳后接口请求成功。

将 A 的启动端口改为 8082 后再次启动,当作系统 B

可以看到这次是无需登陆的,跳到认证中心后就马上跳回了,如果去掉 alert 一般是看不出跳转过程的。

最后在任意一个系统注销,都会让所有的系统推出登陆。

可以看到,在系统 A 登录系统后,系统 B,系统 C 都不再需要输入用户名密码进行登录。如果速度足够快甚至都注意不到调到 SSO 再跳回来的过程。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

时间: 2019-06-12

SpringBoot+Spring Security+JWT实现RESTful Api权限控制的方法

摘要:用spring-boot开发RESTful API非常的方便,在生产环境中,对发布的API增加授权保护是非常必要的.现在我们来看如何利用JWT技术为API增加授权保护,保证只有获得授权的用户才能够访问API. 一:开发一个简单的API 在IDEA开发工具中新建一个maven工程,添加对应的依赖如下: <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-b

SpringBoot 使用jwt进行身份验证的方法示例

这里只供参考,比较使用jwt方式进行身份验证感觉不好,最不行的就是不能退出 登陆时设定多长过期时间,只能等这个时间过了以后才算退出,服务端只能验证请求过来的token是否通过验证 Code: /** * Created by qhong on 2018/6/7 15:34 * 标注该注解的,就不需要登录 **/ @Target({ElementType.METHOD,ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documente

SpringBoot使用JWT实现登录验证的方法示例

什么是JWT JSON Web Token(JWT)是一个开放的标准(RFC 7519),它定义了一个紧凑且自包含的方式,用于在各方之间以JSON对象安全地传输信息.这些信息可以通过数字签名进行验证和信任.可以使用秘密(使用HMAC算法)或使用RSA的公钥/私钥对来对JWT进行签名. 具体的jwt介绍可以查看官网的介绍:https://jwt.io/introduction/ jwt请求流程 引用官网的图片 中文介绍: 用户使用账号和面发出post请求: 服务器使用私钥创建一个jwt: 服务器返

详解SpringBoot+SpringSecurity+jwt整合及初体验

原来一直使用shiro做安全框架,配置起来相当方便,正好有机会接触下SpringSecurity,学习下这个.顺道结合下jwt,把安全信息管理的问题扔给客户端, 准备 首先用的是SpringBoot,省去写各种xml的时间.然后把依赖加入一下 <!--安全--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-secu

Spring Boot Security 结合 JWT 实现无状态的分布式API接口

简介 JSON Web Token(缩写 JWT)是目前最流行的跨域认证解决方案.JSON Web Token 入门教程 这篇文章可以帮你了解JWT的概念.本文重点讲解Spring Boot 结合 jwt ,来实现前后端分离中,接口的安全调用. Spring Security,这是一种基于 Spring AOP 和 Servlet 过滤器的安全框架.它提供全面的安全性解决方案,同时在 Web 请求级和方法调用级处理身份确认和授权. 快速上手 之前的文章已经对 Spring Security 进行

Spring boot整合shiro+jwt实现前后端分离

本文实例为大家分享了Spring boot整合shiro+jwt实现前后端分离的具体代码,供大家参考,具体内容如下 这里内容很少很多都为贴的代码,具体内容我经过了看源码和帖子加了注释.帖子就没用太多的内容 先下载shiro和jwt的jar包 <!-- shiro包 --> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId

spring boot security设置忽略地址不生效的解决

spring boot security设置忽略地址不生效 最近在试下微服务改造,出现这样一个问题所有请求都经过spring cloud gateway进行认证授权后再访问后端数据方服务,但有些需要合作机构回调,由于进行了security认证,最终的方案是对回调地址进行忽略auth认证. 最终security主要代码如下: @Configuration @EnableWebSecurity public class WebSecurityConfig extends WebSecurityCon

Spring boot security权限管理集成cas单点登录功能的实现

目录 1.Springboot集成Springsecurity 2.部署CASserver 3.配置CASclient 挣扎了两周,Spring security的cas终于搞出来了,废话不多说,开篇! 1.Spring boot集成Spring security 本篇是使用spring security集成cas,因此,先得集成spring security新建一个Spring boot项目,加入maven依赖,我这里是用的架构是Spring boot2.0.4+Spring mvc+Spri

Spring Boot Security配置教程

1.简介 在本文中,我们将了解Spring Boot对spring Security的支持. 简而言之,我们将专注于默认Security配置以及如何在需要时禁用或自定义它. 2.默认Security设置 为了增加Spring Boot应用程序的安全性,我们需要添加安全启动器依赖项: <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-

详解Spring Boot Security

简介 Spring Security,这是一种基于 Spring AOP 和 Servlet 过滤器的安全框架.它提供全面的安全性解决方案,同时在 Web 请求级和方法调用级处理身份确认和授权. 工作流程 从网上找了一张Spring Security 的工作流程图,如下. 图中标记的MyXXX,就是我们项目中需要配置的. 快速上手 建表 表结构 建表语句 DROP TABLE IF EXISTS `user`; DROP TABLE IF EXISTS `role`; DROP TABLE IF

解决Spring Boot和Feign中使用Java 8时间日期API(LocalDate等)的序列化问题

LocalDate . LocalTime . LocalDateTime 是Java 8开始提供的时间日期API,主要用来优化Java 8以前对于时间日期的处理操作.然而,我们在使用Spring Boot或使用Spring Cloud Feign的时候,往往会发现使用请求参数或返回结果中有 LocalDate . LocalTime . LocalDateTime 的时候会发生各种问题.本文我们就来说说这种情况下出现的问题,以及如何解决. 问题现象 先来看看症状.比如下面的例子: @Sprin

Spring boot自定义http反馈状态码详解

前言 最近在开发一些http server类型程序,通过spring boot构建一些web程序,这些web程序之间通过http进行数据访问.共享,如下图, 假设现在client发起一次保存数据的请求到server,server可能会返回如下类似的数据 { "status":1, "message":"xxxxxx" } 然后client通过解析json获得status来判断当前的请求操作是否成功,开发过程中通过都是这么做的,但是这样在restf

Spring&nbsp;boot整合security详解

目录 前言 配置依赖 用户配置 1.内存用户存储 2.数据库用户存储 3.LDAP用户存储 4.自定义用户存储 拦截配置 前言 在进行框架选型时最常用的选择就是在Spring security 和Shiro中进行抉择,Spring security 和 shiro 一样,都具有认证.授权.加密等用于权限管理的功能.但是对于Springboot而言,Spring Security比Shiro更合适一些,他们都是Spring生态里的内容,并且在使用上Spring boot只需要引入Security就