springboot如何基于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)。springboot架构图
⽅案⼀共享 cookie
基于 cookie-session 机制的系统中,登录系统后会返回⼀个 sessionId 存储在 cookie 中,如果我们能够让另外⼀个系统也能获取到这个 cookie,不就获取到凭证信息了,⽆需再次登录。刚好浏览器的 co
okie 可以实现这样的效果(详见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 说:不错不错,真⾹。
要注意这⾥有个坑就是:如果另外⼀个恶意系统 C 安装相同的格式跳转到 SSO,想要获取 jwt,这显然是不应该给它的。所以在回跳回去的时候要判断⼀下这个回调地址是不是合法的,能不能给 jwt 给它,可以向后台请求判断也可以在 sso 前台直接写死合法的地址。在 demo 是没有这个判断过程的。
实现业务系统
业务系统代码⾮常简单,主要是⽤了⼀个,拦截 http 请求,提取出 token 向 sso 认证中⼼验证 token 是否有效,有效放⾏,否则返回错误给前端。太简单也不贴代码了,到 github 上看看就明⽩了。
效果
上⾯说了⼀⼤串都是原理了,其实这个难也就难在原理部分,代码实现并没有那么复杂。这⾥就不贴代码了,有需要直接到github 上看。
这⾥上⼏个效果图:
系统 A ⾸次登陆系统
可以看到⾸次登陆是需要跳到 sso 认证中⼼输⼊⽤户名密码进⾏登陆验证的。登陆成功回跳后接⼝请求成功。
将 A 的启动端⼝改为 8082 后再次启动,当作系统 B
可以看到这次是⽆需登陆的,跳到认证中⼼后就马上跳回了,如果去掉 alert ⼀般是看不出跳转过程的。
最后在任意⼀个系统注销,都会让所有的系统推出登陆。
可以看到,在系统 A 登录系统后,系统 B,系统 C 都不再需要输⼊⽤户名密码进⾏登录。如果速度⾜够快甚⾄都注意不到调到 SSO 再跳回来的过程。
以上就是本⽂的全部内容,希望对⼤家的学习有所帮助,也希望⼤家多多⽀持。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论