作用
在一个系统登录后,其他系统也能共享该登录状态,无需重新登录。
演进
cookie → session → token →单点登录
Cookie
可以实现浏览器和服务器状态的记录,但Cookie会出现存储体积过大和可以在前后端修改的问题
Session
为了解决Cookie的数据敏感问题,Session应运而生。
- 常见的实现方式是基于Cookie。只将口令放置Cookie,通过口令实现前后端数据的映射,大部分数据存储于服务器的session中,这里会有一个全局sessions去存储每个用户的session,并设置每个的有效期,一般20分钟。超时则重新生成和更新全局sessions。
- 分布式问题:通常服务器是集群,用户的请求会走到负载均衡,不一定打在同一台登录请求的机器上,那不就 session 失效了吗
- 解决方案:
- 第一种,把 session 集中存储,给个独立的 redis 或普通数据库
- 第二种,将相同 ip 请求的负载均衡都打在同一台机器上。
- 通常是第一种,因为第二种的同一台机器会出现请求过多宕机的问题。
- 解决方案:
token
-
解决场景
session 的维护给服务端造成很大困扰,我们必须找地方存放它,又要考虑分布式的问题,甚至要单独为了它启用一套 Redis 集群。有没有更好的办法?------token
登录场景不需要往 session 存太多东西,那为何不直接打包到 cookie 中呢?这样服务端就不用存了,每次核验 cookie 带的 token 有效性就可以了,还可以存一些轻量的信息。
-
HTTP 头部
token 认证则不需要后台保存,token一般放在HTTP请求头的 Authorization 中
-
流程
- 输入用户密码,通过的话,服务器返回 token
- 浏览器每次携带该 token 去请求数据
来源
最早起源于多服务器间 session 数据一致的问题。
方案
关键如何让 Session ID(或 Token 在多个域中共享)
不同的项目情况,适用不同的方案,适合的才是最好的
按照域名划分方式分类:
同主域
一个企业,一般情况下只有一个域名,通过二级域名区分不同的系统。
二级域名不同也算跨域。当协议,域名,端口其中一个不同时,就算跨域了。
例如 aliyun.com
,其他业务系统分别为:a.aliyun.com
、b.aliyun.com
,要做单点登录,需要一个登录系统,叫做 account.aliyun.com
,我们只要在 acount.aliyun.com
登录了,a.aliyun.com
和 b.aliyun.com
也登陆了
实现:
-
方法一: Root Cookie 方式
-
原理:同一域名下的不同子域名属于同一主域名,主域名下的 cookie 在不同子域名中都是可见的。
-
将得到的 ticket 写入根 Cookie 里,通过修改 domain。这一步前后端择一写即可
利用二级域名写一级域名的 Cookie,
acount.aliyun.com
登录后,可将 cookie 的域设置为顶域,即.aliyun.com
,这样所有的子域都可以访问到顶域的 cookie-
前端:react 和 vue 都有 js-cookie 包
jsximport Cookies from 'js-cookie' Cookies.set('key', 'value', { domain: 'localhost' })
前提:后端没有配置 httpOnly,否则无法通过 document.cookie 方式读写,但有些浏览器仍支持写入。
后端:java
jsxCookie cookie = new Cookie("key", "value");cookie.setDomain('.example.com')
无需在 domain 后加上端口号。设置完后,同主域名的网址打开浏览器的存储,便能看到 cookie 了。
-
-
总结:实现简单,不支持跨主域名的方式
-
-
方法二:postMessage + iframe 方案,无需后端参与
-
可以实现同主域和不同主域,不同的是同主域的实现即便关闭后,再打开也没问题。不同主域的,关闭后就不会保存了。监听 message 的同时,在 postMessage 后,窗口就会触发了。
-
cross-storage 库
,实现了对该方案的封装- 原理:类似中转站,所有的登录信息的存储或获取都通过该中转站
- 使用注意
-
如果无法连接成功的,可以考虑将初始化代码放入 html 中,并单独开启一个服务器(例如小型 node),用来对该页面的访问。
-
CrossStorageHub.init 时,localhost 和 域名都要写上
javascriptCrossStorageHub.init([ {origin: /.*localhost:808\d$/, allow: ['get', 'set', 'del']}, {origin: /.*data.com:300\d$/, allow: ['get', 'set', 'del']} }]
-
-
缺陷:Safari 浏览器不兼容该方案
-
原因:postMessage + iframe(cross-storage)由于 safari 浏览器只支持和 iframe 通信,不支持新窗口通信,无论是通过 iframe.src 带上 token 的 query 还是通过 postMessage 存储到 localStorage 里。新打开一个窗口后,localStorage 里都没有之前的存储值。
-
cross-storage 的文档,也给出了降级方案
- 可以采用 root cookie 方式
- 让服务端返回的方式
- 直接去改变safari浏览器的配置。 (不建议)
- 跳转的 url 链接带上该 token 的 query,前提是要点击后才能触发。再保存到本地
-
-
不同主域
实现:
-
方法一:
部署一个 SSO 认证中心,专门负责处理登录请求(登录、登出、获取用户信息、当前用户状态),是单点登录的标准做法。
类似中转站,所有子系统的登录都要询问一遍这个中转站
CAS: 基于 SSO 认证中心的开源项目代表,Central Authentication Service 即中央认证服务
-
方法二:iframe + postMessage 的方式
同上