定义
单点登录(Single Sign-On,简称 SSO。是目前比较流行的企业业务整合的解决方案之一。
是指用户只需要登录一次,即可在多个应用系统中访问受授权的资源。其原理是在用户登录之后,服务器会颁发一个令牌(Token),并将该令牌保存在共享的存储中(例如 Redis、数据库等),然后在用户访问其他应用系统时,该系统会向认证服务器发送请求,如果该用户已经登录过,则认证服务器会颁发一个新的令牌,否则需要用户重新登录。
eg: 淘宝、天猫都属于阿里旗下,当用户登录淘宝后,再打开天猫,系统便自动帮用户登录了天猫,这种现象就属于单点登录。
实现
方式1:通过session进行验证
session 称为“会话”,session 对象存储特定用户会话所需的属性及配置信息。简言之,session 就是服务器为了储存用户信息而创建的一个验证手段。用户在登录了一个系统后,服务器会将登录信息储存在一个 session 中,产生 session ID,客户端会保存该 ID;当这个用户再登录其他系统时,服务器会自动复制上一个模块的 session 该服务器的 session 中,以获取用户登录信息,实现用户只登录一次,就可以登录其他系统。在用户退出登录时,服务器会自动删除 session。
整个过程均在服务器端完成,用户实际使用时没有感知。
方式2:通过cookie进行验证
cookie 是某些网站为了辨别用户身份,由服务端生成,发给客户端暂时或永久保存的信息。简言之,cookie 就是一种存储用户数据的媒介。举例来说,当我们打开一个网站,比如新浪、CSDN、知乎时,输入用户名和密码登录后系统会弹出是否保存 cookie,如果我们选择保存,在下一次登录时,就不需要再次输入用户名和密码,而是默认登录成功,直接进入页面。
方式3:通过token进行验证
token 在身份认证中是令牌的意思,在词法分析中是标记的意思。一般作为邀请、登录系统使用。简言之,token 就是一种凭证,用户在登录注册时需要获取凭证,在经过验证后,方可登录相关被授权的应用。
用户在首次登录系统时输入账号和密码,服务器会收到登录请求,然后验证是否正确;
服务器会根据用户信息,如用户 ID、用户名、秘钥、过期时间等信息生成一个 token 签名,然后发给用户;
用户验证成功后,返回 token;
前端服务器收到 token 后,存储到 cookie 或 Local Storage 里;当用户再次登录时,会被服务器验证 token;
服务器收到用户登录请求后,对 token 签名进行比对:如果 token 验证正确,用户登录成功;如果 token 验证不正确,用户登录失败,跳转到登录页。
区别
在 Authing 中,无需手动编写操作 session、cookie 或是 token,控制台中可以一键体验单点登录功能,并且支持自建与集成第三方等多种方式,还可以通过 SDK 接入并自定义自己的应用与登录方式。
同域名下的单点登录
cookie
的domain
属性设置为当前域的父域,并且父域的cookie
会被子域所共享。path
属性默认为web
应用的上下文路径。
利用cookie
的这个特点,没错,我们只需要将cookie
的domain
属性设置为父域的域名(主域名),同时将cookie
的path
属性设置为根路径,将Session ID
(或 Token
)保存到父域中。这样所有的子域应用中就都恶意访问到这个cookie
。
不过这要求应用系统的域名需建立在一个共同的主域名之下,如tieba.baidu.com
和map.baidu.com
,它们都建立在baidu.com
这个主域名之下,那么它们就可以通过这种方式来实现单点登录。
不同域名下的单点登录(1)
如果是不同域的情况下,cookie
是不共享的,这里我们可以部署一个认证中心,用于专门处理登录请求的独立Web
服务。
用户统一在认证中心登录,登录成功后,认证中心记录用户的登录状态,并将token
写入cookie
(注意这个cookie
是认证中心的,应用系统是访问不到的)
应用系统检查当前请求有没有token
,如果没有,说明用户在当前系统中尚未登录,那么就将页面跳转至认证中心。
由于这个操作会将认证中心的cookie
自动带过去,因此,认证中心能够根据cookie
知道用户是否已经登录过了。
- 如果认证中心发现用户尚未登录,则返回登录页面,等待用户登录。
- 如果发现用户已经登录过了,就不会让用户再次登录了,而是会跳转回目标
URL
,并在跳转前生成一个token
,拼接在目标URL
的后面,回传给目标应用系统。
应用系统拿到token
之后,还需要像认证中心确认下token
的合法性,防止用户伪造。确认无误后,应用系统记录用户的登录状态,并将token
写入cookie
,然后给本次访问放行。(注意这个cookie是当前应用系统的)当用户再次访问当前应用系统时,就会自动带上这个token
,应用此种实现方式相对复杂,支持跨域,扩展性好,是单点登录的标准做法。
不同域名下的单点登录(2)
可以将Session ID
(或 Token
)保存到浏览器的LocalStorage
中,让前端在每次向后端发送请求时,主动将LocalStorage
的数据传递给服务端。这些都是由前端来控制的,后端需要做的仅仅是在用户登录成功后,将Session ID
(或 Token
)放在响应体中传递给前端。
单点登录完全可以在前端实现。前端拿到Session ID
(或 Token
)后,除了将它写入自己的LocalStorage
中之外,还可以通过特殊手段将它写入多个其他领域下的LocalStorage
中。
关键代码如下:
// 获取token
let token = result.data.token;
// 动态创建一个不可见的iframe,在iframe中加载一个跨域HTML
let iframe = document.createElement("iframe")
iframe.src = "http://app1.com/local.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)
前端通过iframe + postMessage()
方式,将同一份token
写入到多个域下的LocalStorage
中,前端每次向后端发送请求之前,都会主动从LocalStorage
中读取token
,并在请求中携带,这样就实现了同一份token
被多个域所共享。
此种方式完全由前端控制,几乎不需要后端参与,同样支持跨域。
可能会用到的算法
通过key
获取当前浏览器地址URL上的value
function getUrlValue(name) {
let reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)')
let r = window.location.search.substr(1).match(reg)
if (r != null) return unescape(r[2])
return null
}
eg:
// url :https://www.baidu.com?a=1&b=2
getUrlValue(a) // 1
getUrlValue(b) // 2