Leo-lin214 / keep-learning

:books: 好好学习,天天向上
6 stars 0 forks source link

单点登录(SSO)的思考 #19

Open Leo-lin214 opened 4 years ago

Leo-lin214 commented 4 years ago

如果做过大型网站的童鞋们,相信对单点登录肯定不陌生。就好比如天猫和淘宝,当我在淘宝处成功登录之后,再跳去天猫网站,你会发现,在天猫处也会神奇滴登录了。🤔

为什么会这样?没错,就是今天的主角——单点登录的“锅”。

毫无疑问,对于一个拥有多套子系统的公司来说,单点登录是必须实现的,它不仅可以减少开发者对于各个子系统对应的登录功能维护,还能更好滴提升用户体验。

那么问题来了,对于一个单点登录(SSO)的功能实现,可以从哪方面思考呢?下面我们就来一起探讨一下啦...

目录

  1. 传统登录和JWT
  2. 如何实现单点登录?

传统登录和JWT

在讲解之前,我觉得是很有必要提提登录的基本实现的。就好比如,如果我要让你实现一个登录功能,你会如何实现呢?

相信很多童鞋都会知道使用 Session 和 Cookie 实现,那么还有吗?哈哈,相信你也能看到,就是时下最为常用方式 JWT。那么问题来了,它们之间有什么区别?甭急,下面将会简单讲解一下。

传统登录

传统登录的实现其实就是使用 Session 和 Cookie 的,那先看看它们的处理流程是怎样。

  1. 客户端发送登录请求到服务器。
  2. 服务器将会根据客户端传过来的用户名和密码创建一个 session,session 中存储了相应用户角色、登陆时间等等信息。
  3. 服务器向客户端返回一个 sessionid,并写入客户端的 cookie(cookie 其实就相当于是一个用户凭证)。
  4. 客户端接下来的每次请求都会默认带上 cookie ,相当于将 sessionid 传回服务器。
  5. 服务器收到 sessionid,会找到前期保存的数据,由此得知用户的身份。

利用 Session 和 Cookie 技术实现的传统登录功能,过程并不难理解,简单滴说,就是服务端负责管理用户状态和验证,而客户端则负责存储用户状态

那么问题来了,服务端返回的 SessionId 一定是要使用 Cookie 存储吗?还是说可以存储在 SessionStorage 或者 LocalStorange 上?🤔

对于这个问题,其实也是我一直思考的,定性思考一番,其实也是有它的目的。首先可以明确的是,服务端返回的 SessionId 不一定要使用 Cookie 进行存储的,可以使用诸如 SessionStorage、LocalStorage 等方式进行存储的

那么使用 Cookie 存储服务端返回的 SessionID 有什么好处?主要是为了减轻前端开发者负担,由于服务端可以直接通过设置返回头 Set-Cookie 来直接在客户端的 Cookie 中存储 SessionId。这样一来,客户端就可以免去了手动保存 SessionId 的步骤,直接处理相应登录业务逻辑即可。

虽然使用 Cookie 可以很方便开发者的存储相应的用户状态,但是却会导致诸如 XSS、 CSRF 等安全性问题。

JWT

使用 Session 和 Cookie 技术实现的登录功能依然存在着缺陷。

首先就是 Cookie 允许被用户禁止,当一个浏览器禁止使用 Cookie 后,那么服务器中存储的 Session 也被禁止,直接导致相应的用户无法进行登录。另外,当用户访问量很大时,对服务器的压力也会大,因为用户访问量大将会触使服务器需要管理大量用户的 Session,想象一下,访问量达到亿级别时会怎样?毫无疑问,服务器管理的 Session 越多,对服务器本身压力也会大。

那么,有没有一种方式可以克服以上的缺陷呢?有~那便是 JWT。

JWT 是 Json Web Token 的缩写,一个规范用于用户和服务器之间传递安全可靠的信息。(简单滴说,就是所谓的 Token 机制)。让我们先看看 Token 机制的处理流程。

  1. 客户端发送请求到服务器。
  2. 服务返回一个经过加密的token,并由客户端负责存储,可存到 local storage 或 cookie。
  3. 客户端接下来的每次请求,都需要手动加上该 token。
  4. 服务器对 token 进行解码,如果 token 有效,则处理请求。
  5. 一旦用户登出,客户端就需要销毁 token。

Token 机制相对于传统登录方式的最大区别就是,所有的用户状态都交给客户端进行管理和存储

Token 可以看成是一张门票,当你想去看演唱会时,那必须得先展示门票。

一个 JWT 实际上就是一串字符串,由三部分组成,分别是

// 一个完整的Token可表示为
Token = encodeBase64(header) + '.' + encodeBase64(payload) + '.' + encodeBase64(signature)

Token 机制由于无需在服务端管理用户状态,因此极大滴减轻了服务器的压力,另一方面,由于在登录后的任何请求,都不会再在请求中带上用户标识,这样一来,就可以极大滴避免 CSRF 攻击。

如何实现单点登录?

在大型网站下,实现单点登录变得越来越重要,那么要实现单点登录具体有哪些方案呢?

要实现单点登录,个人总结认为,主要有 Session 共享机制、Cookie 跨域处理、CAS 服务 三种方案。(当然可能还有其他方案,欢迎各位大佬们来分享分享啦 😄)。那么我们就来简单分析一下。

Session 共享的实现,大部分工作都是需要后端配合的,一般涉及到集群方面等,当然如果使用的是 Node的话,可以使用 Redis 实现,将所有的 Session 都放在中间层管理。

下面就讲讲 Cookie 跨域处理和 CAS 服务两种方案,这两种方案也是前端开发者常常需要考虑的方案啦。

Cookie 跨域处理

如果你使用过 Cookie 的话,肯定会知道 Domain 。那么 Domain 究竟是拿来干嘛的?

Cookie 中 Domain 就是拿来约束使用 Cookie 的域名管理。举个例子,如果网站a.test.comb.test.com是可以通过 Cookie 进行通信的,因为 Domain 会被设置为test.com,相反如果网站a.test.comc.haha.com就无法通过 Cookie 进行通信,因为网站c.haha.com无法受到 Domain 限制而无法获取相应的 Cookie 值。

先回顾一下跨域的同源政策,只有同协议、同域名、同端口才符合同源政策

那么在跨域情况下,需要如何让 Cookie 进行通信呢?

  1. 当用户在网站a.test.com进行登录,后端根据用户名和密码判断该用户是网站c.haha.com中用户,会先在a.test.com登录后再返回标识给客户端。

  2. 客户端先配置好 axios 的请求头部字段withCredentials为 true。

    axios.defaults.withCredentials = true

    同时,运维需要在 Ngix 中配置接口允许 CORS,并且设置相应的响应头部字段。

    access-control-allow-credentials: true
    access-control-allow-origin: https://a.test.com

    必须要注意的是,CORS 在允许跨域处理 Cookie 时绝对不能设置 access-control-allow-origin 值为 *,只能针对性的域名

  3. 客户端根据标识调用相应网站的登录接口(如c.haha.com/ligon)去登录,登录成功后,后端会在c.haha.com的 Cookie 中存储 Token 值(此时还是在网站a.test.com中),这时候就可以跳转到c.haha.com主页。

  4. 去到c.haha.com主页后,获取相应 Cookie 中 Token 值配置在请求头部字段 authorization中,便可实现单点登录功能。

在上面的基本流程中,相信你肯定会有问题,甭急,让我们一个个来解决。

在 axios 的请求头部字段设置withCredentials的值为 true 有何用?

其实,withCredentials字段用于允许跨域处理 Cookie。但是单纯配置它是没法起效果的,必须要结合运维在响应头部加上 access-control-allow-credentials 和 access-control-allow-origin 两个字段才能起效果

既然在 CORS 中的 access-control-allow-origin 响应字段必须为针对性的域名而不能设置为 *,那么在本地开发时,如何配置?

要处理这个问题,有两种方案,一种是在 access-control-allow-origin 响应字段中增添一个域名为 localhost。另一种则是配置 webpack-dev-server 中 proxy,如下:

module.exports = {
  // ...
  proxy: {
    '/test/login': {
      traget: 'http://c.haha.com/login'
    },
    onProxyRes: function(proxyRes, req, res) { // 接口返回中间层处理
      // 针对c.haha.com/login登录接口返回手动存储cookie
    }
  }
  // ...
}

时下,使用 Cookie 跨域处理是改动最小方案,也是目前最优的选择方案。

CAS 服务

CAS 服务,相当于所有的网站的登录服务,都统一使用一个登录网站处理。

CAS 分为两部分,分别是 CAS Client(中间登录客户端)和 CAS Server(登录处理服务器)。在现实生活中还是挺多使用到 CAS 的,例如淘宝和天猫网站便是,未登录情况下,无论在淘宝上还是在天猫上点击登录都会跳去相同的登录页面,而这个登录页面其实就是 CAS Client。当登录成功后,会发现无论是在天猫还是淘宝都会是登录状态。

在了解 CAS 服务前,需提前了解以下几个知识点。

接下来,我们就来瞅瞅,CAS 服务的流程是如何的。

  1. 用户第一次访问a.test.com网站时,点击登录会重定向到 CAS Server,发现请求中没有 Cookie,那么便会返回状态 302,重定向到 CAS Client 登录页面。

    http://cas.com/login?previosurl=http://a.test.com
  2. 在 CAS Client 中输入用户名和密码并且点击提交后,CAS Server便会生成一个 TGT,再用 TGT 生成一个 ST,然后便会在返回头中 Set-Cookie ,在客户端 Cookie 中存储 ST。

  3. 存储成功后,便重新回到上面的 previosurl 中,并带上一个 ticket 参数(用于后端验证用的)。

    http://a.test.com?ticket=ST-123456-asdasd4fas23qf8

    ticket 后面的值就是 ST。

  4. 回到a.test.com网站后,将参数 ticket 传递给服务端,服务端会将 ticket 拿到 CAS Server 进行验证,验证通过后便会返回登录成功。

  5. 用户第一次访问c.haha.com后,点击登录同样会重定向到 CAS Server 中,这时候由于上一次已经在 CAS Client 客户端的 Cookie 中存储了 ST,所以 CAS Server 会发现请求头部已经带上了上一次登录的 ST,这时候便会直接同定向回c.haha.com中,并且带上参数ticket

    http://c.haha.com?ticket=ST-123456-asdasd4fas23qf8
  6. 回到c.haha.com后,同样会拿 ticket 传递到服务端,服务端拿到 ticket 后便会再次拿到 CAS Server 中进行验证,验证通过便会成功登录。至此,一个单点登录功能便实现。

可以看到的是,CAS 服务原理就是利用中间客户端 CAS Client 复用 Cookie 值实现单点登录功能