如何设计 SSO
什么是 SSO
一个 SSO(单点登录)系统,能做到一次登录,能在多个系统中获得登录态,而不需要在每一个系统中再单独登录一次
如何实现
在 Web 系统中,不同的系统一般指的是不同域名的站点,如 foo.example.com
, bar.example.com
。登录态的保持可以用 Cookie、localStorage 这种持久化的方式来保存登录凭证。Cookie 可以通过后端接口返回的 Response Header 中的 Set-Cookie
来设置,localStorage 则可以通过后端接口返回的数据自行保存,并在接口请求的时候带上对应的登录凭证。
Set-Cookie 的极限
Cookie 的优势是借助浏览器的机制(Set-Cookie
)可以自己设置上去,并且可以设置到二级域名上,并且在发送请求的时候,浏览器也会自动给请求头带上 Cookie。
假设我们多个系统的二级域名都是 example.com
,如 foo.example.com
,bar.example.com
,我们可以通过如下方式将 Cookie 设置到 example.com
,这样,所有二级域名为 example.com
的站点全都变成登录态了:
但是,如果多个系统的二级域名不同呢?比如阿里旗下有 taobao.com
,tmall.com
,这样如何实现 SSO 呢?
不同二级域名的 SSO
假设我们有三个系统(虚构的,请不要访问)
- sso.example.com:统一登录页面,当一个系统未登录的时候,会统一跳到统一登录页进行登录
- mall.foo.com:商城系统
- chat.bar.com:聊天系统
可以看到,这三个系统的二级域名均不同,下面介绍几种方式
登录后携带 ticket 转跳各个系统
我们在 sso.example.com 登录后,可以携带一个 ticket 转跳到其他系统,其他系统再转跳到下一个系统,ticket 可以理解成一个可以向后端接口交换到最终登录凭证的参数,比如后端接口可以 Set-Cookie:
这种方式不管再什么浏览器版本中都是可行的,缺点是会有一个比较漫长的登录过程,如果系统比较多的情况下,可能体验不是很好。
另一种同样原理的方式是不去一次性跳转所有系统,而是在访问系统的时候,如果发现是未登录状态,则跳转到统一登录页,如果此时统一登录页已经是登录态的话,再携带 ticket 跳转回原系统,缺点是访问未登录系统的时候必定会跳转统一登录页一次。
如何更快
那么,有没有办法登录一次,其他系统就自动变成登录态,而不需要跳转页面呢?过去会有下面这样的做法,但在当今时代已经不适用了,我先介绍一下方式,然后说为什么不适用了。
- 我们可以在统一登录页登录,在登录成功之后,在统一登录页调用
*.foo.com
的接口和*.bar.com
的接口,让他们 Set Cookie 到各自的域名。 - 通过 iframe,在统一登录页加载
*.foo.com
和*.bar.com
的页面,或者在*.foo.com
或者*.bar.com
通过 iframe 加载*.example.com
,并通过 postMessage 的方式去让对应的页面设置 Cookie
但是这些方式在当今主流浏览器中均已不适用了,原因是出于对用户隐私的保护,浏览器开始限制写入第三方 Cookie(当然有方法可以关闭,但因为这个限制存在,上面的方法不再是通用的解决方案了),比如你在 sso.example.com 下,访问 *.foo.com 的接口
,*.foo.com
的接口想要去设置 Cookie,因为域名不同,其想要设置的 Cookie 就是第三方 Cookie,关于第三方 Cookie,我曾经写过一篇文章提到过:Cookie 知识二则,你也可以看看这篇文章:当浏览器全面禁用三方 Cookie
怎么办
首先上面说的都是 Cookie 方式,比如通过 iframe,你仍然可以通过 localStorage 对 token 进行存储,然后自己发送请求的时候带上 token 即可,这没有 Cookie 方便,不过也是可以实现 SSO 的,但我也在 StackOverflow 看到有人提问,说通过这种方式不能在 iframe localStorage 中保存数据,我在最新版本的 Chrome 上测试过是可行的。
总结
在当前浏览器环境下,可行的方式:
- 统一登录,并一次性转跳各个系统获取登录态
- 统一登录,在访问各自系统时转跳回统一登录再转跳回系统获取登录态
- 使用 Cookie 之外的方式,如 localStorage,加载 iframe 并传递必要信息给各个系统
相关的题外话
- 跨域请求,客户端必须设置 withCredentials(XMLHttpRequest) 或者 credentials=include(Fetch),Set-Cookie 才能正确设置,否则会被忽略
- 在 Chrome 中,谷歌的登录系统通过插件和浏览器中统一登录状态,因此体验挺好的