单点登录(SSO)的实现方式


一、什么是单点登录?

单点登录(Single Sign On),简称为 SSO,是比较流行的企业业务整合的解决方案之一。SSO的定义是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。

二、背景 & 前言

在 B/S 系统中,登录功能通常都是基于 Cookie 来实现的。一般有两种形式。第一种:当用户登录成功后,一般会将登录状态记录到 Session 中。第二种是:给用户签发一个 Token。然而这两种形式,均需要在客户端保存一些信息(Session ID 或 Token ),并要求客户端在之后的每次请求中携带它们。

在这样的场景下,使用 Cookie 无疑是最方便的,因此我们一般都会将 Session 的 ID 或 Token 保存到 Cookie 中,当服务端收到请求后,通过验证 Cookie 中的信息来判断用户是否登录 。

单点登录是指在同一帐号平台下的多个应用系统中,用户只需登录一次,即可访问所有相互信任的应用系统。

举例来说,百度贴吧和百度地图是百度公司旗下的两个不同的应用系统,如果用户在百度贴吧登录过之后,当他访问百度地图时无需再次登录,那么就说明百度贴吧和百度地图之间实现了单点登录。

单点登录的本质就是在多个应用系统中共享登录状态。如果用户的登录状态是记录在 Session 中的,要实现共享登录状态,就要先共享 Session,比如可以将 Session 序列化到 Redis 中,让多个应用系统共享同一个 Redis,直接读取 Redis 来获取 Session。

当然仅此是不够的,因为不同的应用系统有着不同的域名,尽管 Session 共享了,但是由于 Session ID 是往往保存在浏览器 Cookie 中的,因此存在作用域的限制,无法跨域名传递,也就是说当用户在 app1.com 中登录后,Session ID 仅在浏览器访问 app1.com 时才会自动在请求头中携带,而当浏览器访问 app2.com 时,Session ID 是不会被带过去的。实现单点登录的关键在于,如何让 Session ID(或 Token)在多个域中共享。

三、实现单点登录(SSO)的方式

实现单点登录的方式有三种:

1. 父域Cookie

如上所举例,保持cookie放置在一级域名之中,二级域名会自动继承来自父域(一级域名)的Cookie,因此就会实现一次登录,多个业务模块共享登录状态。此种方式实现比较简单,但是具有局限性,不可跨主域。

2. 认证中心

认证中心,是一个专门负责处理登录,鉴权,状态检查的专用模块,一般用于大型企业之内的各级部门协同工作,能较好实现一次登录,多个部门,多个不同主域的登录状态共享。目前我司内部就是采用这种认证中心,涉及到要校验用户权限时,就会跳转到一个专有的页面进行帐号登录去确认状态。
一般用的是Apereo CAS,它是一个企业级单点登录系统。稍后我会去针对这些进行详细的补充。。。。

3. LocalStorage跨域

目前来说,浏览器的安全策略是越来越严格,特别是浏览器新的版本更是控制了Cookie的Same Site属性,无意Cookie受限条件更多,可应用场景变的不再那么广泛。我们可以针对SSO这一块,考虑使用LocalStorage进行单点登录的实现。

在前后端分离的情况下,完全可以不使用 Cookie,我们可以选择将 Session ID (或 Token )保存到浏览器的 LocalStorage 中,让前端在每次向后端发送请求时,主动将 LocalStorage 的数据传递给服务端。这些都是由前端来控制的,后端需要做的仅仅是在用户登录成功后,将 Session ID (或 Token )放在响应体中传递给前端。
在这样的场景下,单点登录完全可以在前端实现。前端拿到 Session ID (或 Token )后,除了将它写入自己的 LocalStorage 中之外,还可以通过特殊手段将它写入多个其他域下的 LocalStorage 中。

代码实现:

// 获取 token
var token = result.data.token;

// 动态创建一个不可见的iframe,在iframe中加载一个跨域HTML
var iframe = document.createElement("iframe");
iframe.src = "http://app1.com/localstorage.html";
document.body.append(iframe);
// 使用postMessage()方法将token传递给iframe
setTimeout(function () {
    iframe.contentWindow.postMessage(token, "http://app1.com");
}, 4000);
setTimeout(function () {
    iframe.remove();
}, 6000);

// 在这个iframe所加载的HTML中绑定一个事件监听器,当事件被触发时,把接收到的token数据写入localStorage
window.addEventListener('message', function (event) {
    localStorage.setItem('token', event.data)
}, false);

声明:Xuhao's Blog|版权所有,违者必究|如未注明,均为原创|本网站采用BY-NC-SA协议进行授权

转载:转载请注明原文链接 - 单点登录(SSO)的实现方式


Carpe Diem and Do what I like