在一个系统登录后,其他系统也能共享该登录状态,无需重新登录。
cookie → session → token →单点登录
可以实现浏览器和服务器状态的记录,但Cookie会出现存储体积过大和可以在前后端修改的问题
为了解决Cookie的数据敏感问题,Session应运而生。
解决场景
session 的维护给服务端造成很大困扰,我们必须找地方存放它,又要考虑分布式的问题,甚至要单独为了它启用一套 Redis 集群。有没有更好的办法?——token
登录场景不需要往 session 存太多东西,那为何不直接打包到 cookie 中呢?这样服务端就不用存了,每次核验 cookie 带的 token 有效性就可以了,还可以存一些轻量的信息。
HTTP 头部
token 认证则不需要后台保存,token一般放在HTTP请求头的 Authorization 中
流程
最早起源于多服务器间 session 数据一致的问题。
关键如何让 Session ID(或 Token 在多个域中共享)
不同的项目情况,适用不同的方案,适合的才是最好的
按照域名划分方式分类:
一个企业,一般情况下只有一个域名,通过二级域名区分不同的系统。
二级域名不同也算跨域。当协议,域名,端口其中一个不同时,就算跨域了。
例如 aliyun.com
,其他业务系统分别为:a.aliyun.com
、b.aliyun.com
,要做单点登录,需要一个登录系统,叫做 account.aliyun.com
,我们只要在 acount.aliyun.com
登录了,a.aliyun.com
和 b.aliyun.com
也登陆了
实现:
原理:同一域名下的不同子域名属于同一主域名,主域名下的 cookie 在不同子域名中都是可见的。
将得到的 ticket 写入根 Cookie 里,通过修改 domain。这一步前后端择一写即可
利用二级域名写一级域名的 Cookie, acount.aliyun.com
登录后,可将 cookie 的域设置为顶域,即 .aliyun.com
,这样所有的子域都可以访问到顶域的 cookie
前端:react 和 vue 都有 js-cookie 包
import Cookies from 'js-cookie'
Cookies.set('key', 'value', { domain: 'localhost' })
前提:后端没有配置 httpOnly,否则无法通过 document.cookie 方式读写,但有些浏览器仍支持写入。
后端:java
Cookie cookie = new Cookie("key", "value");cookie.setDomain('.example.com')
无需在 domain 后加上端口号。设置完后,同主域名的网址打开浏览器的存储,便能看到 cookie 了。
总结:实现简单,不支持跨主域名的方式
可以实现同主域和不同主域,不同的是同主域的实现即便关闭后,再打开也没问题。不同主域的,关闭后就不会保存了。监听 message 的同时,在 postMessage 后,窗口就会触发了。
cross-storage 库
,实现了对该方案的封装
如果无法连接成功的,可以考虑将初始化代码放入 html 中,并单独开启一个服务器(例如小型 node),用来对该页面的访问。
CrossStorageHub.init 时,localhost 和 域名都要写上
CrossStorageHub.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 的文档,也给出了降级方案
实现:
方法一:
部署一个 SSO 认证中心,专门负责处理登录请求(登录、登出、获取用户信息、当前用户状态),是单点登录的标准做法。
类似中转站,所有子系统的登录都要询问一遍这个中转站
CAS: 基于 SSO 认证中心的开源项目代表,Central Authentication Service 即中央认证服务
方法二:iframe + postMessage 的方式
同上