前言
-
最近公司需要做一个SSO单点登录系统,于是上网百度了一些文章,全是一些很模糊的概念,实战起来也很麻烦,这里分享下一个比较简单实用的SSO单点登录方案.
-
单点登录 SSO 全称 Singn Sign On 。SSO 是指在多个应用系统中,用户只需要登录一次用户系统,就可以访问其他互相信任的应用系统。例如:在淘宝登录账户,那么再进入天猫等其他业务的时候会自动登录。另外还有一个好处就是在一定时间内可以不用重复登录账户。
废话不多说直接上视频,看看是不是想要的效果
四个参数 loginname password type info
client1 初始化 token loginname type info都为空,点击登录创建token(实际项目是有个SSO登录系统的,点击登录的时候把token存储到localStorage即可),这个时候来到clinet2 refresh 刷新token已经传递过来了,在client1退出的时候,回到client2 refresh也是退出状态.达到了SSO登录 client1登录,client1也是登录状态,client1 退出,client2也是退出状态
到这里有的小伙伴就问了那我什么时候执行 refresh 事件呢? 答案是: 页面请求的每一个接口相应拦截(比如vue项目就使用axios统一拦截器)
那么是怎么实现跨域能让SSO登录了 client1, client2 都能拿到SSO的token呢?
使用iframe + postMessage 跨域通信(注意加上密钥验证)
-
接收信息
const receiveMsg = function(event) { const user = event.data if (user.token) { } } window.addEventListener('message', receiveMsg, false)
2. 发送信息
const monitor = document.getElementById(id) monitor.contentWindow.postMessage( { user }, // sso地址 html sso.html )
3. 代码说明
-
sso做的事情: 获取本地token发送全局信息出去
-
client做的事情: 发送指定信息(get, undata),通过接收window.addEventListener('message', fun..., false)接收信息 do someing ...
4. 代码展示
5. sso.html
'use strict' class Sso { state = { // 密钥 secretKey: 'SSO-DATA', // remove id removeId: 'remove', } init = () => { document.getElementById('sso').innerText = 'SSO data sharing center' window.addEventListener('message', (e) => this.receiveMsg(e), false) // 初始化 window.parent.postMessage( { init: 'init' }, '*' ) } // 监听事件 receiveMsg = (e) => { const data = e.data if (data) { // 退出标识符 const removeId = this.state.removeId const user = data.user if (user) { const { secretKey } = user if (!secretKey) { throw '密钥不能为空!' } else if (window.atob(secretKey) !== this.state.secretKey) { throw '密钥错误!' } if (user.type && user.type === 'updata') { // 更新user const { loginname, token, password } = user localStorage.setItem( 'user', JSON.stringify({ loginname, token, password, }) ) this.setCookie('loginname', loginname, 1) this.setCookie('token', token, 1) window.parent.postMessage({ loginname, token, password }, '*') } else { // 查找本地 user const localUser = localStorage.getItem('user') // 查找本地 cookies const userCookie = this.getCookie('token') if (localUser) { const { loginname, token, password } = JSON.parse(localUser) if ( (token && token !== removeId) || (password && password !== removeId) ) { if (userCookie && userCookie === removeId) { // cookies 退出登录状态 window.parent.postMessage( { token: removeId }, '*' ) } else if (userCookie && userCookie !== removeId && userCookie !== 'undefined' && userCookie !== 'null' && userCookie !== token) { // cookies 和 local的token不一致 const newUser = { loginname: this.getCookie('loginname'), token: userCookie, } localStorage.setItem('user', JSON.stringify(newUser)) window.parent.postMessage(newUser, '*') } else { // 正常放行 window.parent.postMessage({ loginname, token, password }, '*') } } else if (userCookie && userCookie !== removeId) { // 如果cookies有token if (token === removeId) { // local 退出登录状态 window.parent.postMessage( { token: removeId }, '*' ) } else { const userObj = { loginname: this.getCookie('user'), token: userCookie } window.parent.postMessage(userObj, '*') } } else { window.parent.postMessage( { loginname, token, password }, '*' ) } } else { window.parent.postMessage( { token: removeId }, '*' ) } } } else { window.parent.postMessage( { data }, '*' ) } } } // 存储二级域名 setCookie = (cname, cvalue, exdays) => { const d = new Date() d.setTime(d.getTime() + exdays * 24 * 60 * 60 * 1000) const expires = 'expires=' + d.toGMTString() let hostArr = window.location.hostname.split('.') // 注意 cookies 只有 '同级' 域名 才能共享 (这里只取最后两位) let cdomain = hostArr.slice(-2).join('.') const domain = 'domain=' + cdomain document.cookie = `${cname}=${cvalue};${expires};${domain};path=/` } getCookie = (cname) => { const name = cname + '=' const ca = document.cookie.split(';') for (let i = 0; i < ca.length; i++) { const c = ca[i].trim() if (c.indexOf(name) == 0) { return c.substring(name.length, c.length) } } return '' } checkCookie = (cname, cvalue, exdays) => { this.setCookie(cname, cvalue, exdays) } } window.onload = function () { new Sso().init() }
6. client.html
如果在vue,react里面使用的话,需要全局拦截(router.beforeEach),iframe收到sso发送的token信息再next(),react同理...
里面还有一些比较有意思的地方, 感兴趣的同学可在评论区一起探讨
-
client1登陆的是zs1, client2切换到zs2,client1是怎么切换到zs2的
-
client1 cookies存储的是zs1,client2切换到zs2, client1 怎么把zs1 的 cookies也切换成zs1的
-
client1 登录zs1, client2也是zs1,client2重新登录zs1,如何把client1替换最新的token
二级域名下可共享cookies(cookies有很多限制,首先拿local,再是cookies)
cookies的话二级域名相等可直接拿token,这里不多说了...
旧人以旧/sso-frontendgitee.com
hzd1219809916/sso-frontend: 前后端分离 SSO单点登录 可用于vue react jsp jq,跨域专用 iframe + postMessage 二级域名主域名 最全SSO单点实战 (github.com)github.com
原创,转载请标注!!!