前后端分离 SSO单点登录 可用于vue react jsp jq,跨域专用 iframe + postMessage 二级域名主域名 最全SSO单点实战

前言

  1. 最近公司需要做一个SSO单点登录系统,于是上网百度了一些文章,全是一些很模糊的概念,实战起来也很麻烦,这里分享下一个比较简单实用的SSO单点登录方案.

  2. 单点登录 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 跨域通信(注意加上密钥验证)

  1. 接收信息

 
 

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同理...

里面还有一些比较有意思的地方, 感兴趣的同学可在评论区一起探讨

  1. client1登陆的是zs1, client2切换到zs2,client1是怎么切换到zs2的

  2. client1 cookies存储的是zs1,client2切换到zs2, client1 怎么把zs1 的 cookies也切换成zs1的

  3. client1 登录zs1, client2也是zs1,client2重新登录zs1,如何把client1替换最新的token

二级域名下可共享cookies(cookies有很多限制,首先拿local,再是cookies)

cookies的话二级域名相等可直接拿token,这里不多说了...


旧人以旧/sso-frontend​gitee.com

hzd1219809916/sso-frontend: 前后端分离 SSO单点登录 可用于vue react jsp jq,跨域专用 iframe + postMessage 二级域名主域名 最全SSO单点实战 (github.com)​github.com

原创,转载请标注!!!

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
SSO (Single Sign-On) 单点登录是指在访问多个系统或应用程序时,用户只需登录一次就可以访问所有的系统,而无需再次输入用户名和密码。基于cookie二级域名跨域共享是指在跨域访问的情况下,通过设置cookie的域名和路径,使得不同域名下的系统能够共享登录状态。 具体来说,当用户成功登录一个系统后,该系统会生成一个包含用户登录状态的cookie,并设置该cookie的域名为当前系统的二级域名。然后,该cookie会被发送给浏览器保存,在用户访问其他系统时,浏览器会自动通过cookie将用户的登录状态传递给其他系统。 为了实现跨域共享,所有需要实现SSO的系统的二级域名需要设置为相同的根域名。例如,系统A的域名为a.example.com,系统B的域名为b.example.com,则它们的根域名为example.com。为了在这两个系统之间实现跨域共享,可以将cookie的域名设置为.example.com,这样两个系统就可以共享同一个cookie。 当用户访问系统A时,系统A会检查是否存在含有登录状态的cookie,如果存在则表示用户已经登录,可以直接访问系统A的资源。如果用户访问系统B,系统B也会检查是否存在含有登录状态的cookie,如果存在则表示用户已经登录,可以直接访问系统B的资源。 通过基于cookie二级域名跨域共享的方式,SSO单点登录实现了用户在不同系统间的无缝登录体验,提高了用户的使用便捷性和系统的安全性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值