手把手教你学会 基于JWT的单点登陆

塞尔达,林克

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

本篇主要内容以下:java

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

注意:
  SSO 这个概念已经出现好久好久了,目前各类平台都有很是成熟的实现,好比OpenSSOOpenAMKerberosCAS等,固然不少时候成熟意味着复杂。本文不讨论那些成熟方案的使用,也不考虑 SSO 在 CS 应用中的使用。git

什么是 SSO

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

如何实现 SSO

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

  而后咱们能够考虑考虑实现 SSO 的核心是什么?答案就是如何让一个平台 A 登陆后,其余的平台也能获取到平台 A 的登陆信息(在 cookie-session 机制中就是 sessionId)。redis

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

  cookie 容许同域名(或者父子域名)的不一样端口中共享 cookie,这点和 http 的同域策略不同(http 请求只要协议、域名、端口不彻底相同便认为跨域)。所以只需将多个应用前台页面部署到相同的域名(或者父子域名),而后共享 session 便可以实现单点登陆。架构以下:json

共享session架构

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

方案二 基于回调实现

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

基于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 首次登录系统
    系统A登录gif

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

  • 将 A 的启动端口改成 8082 后再次启动,看成系统 B
    系统B登录gif

能够看到此次是无需登录的,跳到认证中心后就立刻跳回了,若是去掉 alert 通常是看不出跳转过程的。

最后在任意一个系统注销,都会让全部的系统推出登录。

能够看到,在系统 A 登陆系统后,系统 B,系统 C 都再也不须要输入用户名密码进行登陆。若是速度足够快甚至都注意不到调到 SSO 再跳回来的过程。

源码:github

本篇原创发布于:www.tapme.top/blog/detail/2019-03-01-18-52

相关文章
相关标签/搜索