window.postMessage 在iframe父子页面数据传输,页面之间跨域共享、传输数据,实现单点登录
项目需求
实现单点登录多后台,比如 登录淘宝后,跳转链接到天猫时,天猫处于登录中的状态。
实现方式
基于 Iframe+Postmessage,相互之间发送登录token,实现SSO(SingleSignOn)单点登录功能。
实例
期望结果:假设登录昆仑后台拿到token后,跳转到邻汇吧后台时,页面处于登录状态。
方案一:
-
登录昆仑后台拿到token后,跳转到邻汇吧后台时,向邻汇吧内嵌隐藏的昆仑 iframe 发送 getToken 的消息
iframe.contentWindow.postMessage('getToken', targetOrigin)
-
昆仑后台收到消息后(判断 window.parent 是否为邻汇吧后台域名),判断消息是否为 getToken,如果是,发送消息(携带token)给父级
window.parent.postMessage(token, targetOrigin);
-
邻汇后台监听 message,收到消息(data.data),判断来源(data.origin)是否为昆仑后台,存储到 localStorage 中
window.addEventListener('message', data => {}, false);
-
以服务的方式分别打开两个html文件,修改各自的 origin、targetOrigin 后,kunlun 页面点击发送按钮,linhui 页面查看是否刷新显示了token
linhui.html
<body>
<h2>邻汇后台接收token 方案一</h2>
<div>
token:
<span class="token"></span>
</div>
<iframe class="iframe" src="" frameborder="0"></iframe>
<button onclick="getToken()">从iframe拿到token</button>
<script>
let origin = 'http://localhost:7788/linhui.html'
let targetOrigin = 'http://localhost:7788/kunlun.html';
let iframe = document.querySelector('iframe');
if (iframe) {
iframe.src = targetOrigin;
}
// 方案一
function getToken() {
// 发送消息给 iframe,让他通过 window.parent.postMessage 发送的消息返回token
iframe.contentWindow.postMessage('getToken', targetOrigin)
}
// 监听 iframe 通过 window.parent.postMessage 发送的消息
window.addEventListener('message', data => {
let token = data.data
console.log('message', token);
localStorage.setItem('token', token);
}, targetOrigin)
// 以下只是轮询更新页面上调试用显示的token,和方案无关
setInterval(() => {
fn();
}, 1000);
function fn() {
let token = localStorage.getItem('token');
document.querySelector('.token').innerText = token;
// console.log('设置token');
}
</script>
</body>
kunlun.html
<body>
<h2>昆仑后台发送token</h2>
<div>
token:
<span class="token"></span>
</div>
<script>
// https://www.cnblogs.com/congxueda/p/11825820.html
let origin = 'http://localhost:7788/kunlun.html'
let targetOrigin = 'http://localhost:7788/linhui.html';
let iframe = document.querySelector('iframe');
if (iframe) {
iframe.src = targetOrigin;
}
let token = null;
function createToken() {
token = Math.floor(Math.random() * 10000000);
return token;
}
// 方案一
window.addEventListener('message', data => {
if (data.data === 'getToken') {
token = createToken();
window.parent.postMessage(token, targetOrigin);
}
}, false);
// 以下只是轮询更新页面上调试用显示的token,和方案无关
setInterval(() => {
fn();
}, 1000);
function fn(){
let token = localStorage.getItem('token');
document.querySelector('.token').innerText = token;
// console.log('设置token');
}
</script>
</body>
方案二(推荐方案二,逻辑简单,子页面只需要接收token即可,昆仑后台内嵌多个项目iframe,统一发送token)
-
登录昆仑后台拿到token后,发送消息给内嵌的 邻汇吧后台iframe(邻汇吧后台专门提供一个空白页的路由给iframe使用)
iframe.contentWindow.postMessage(token, targetOrigin)
-
邻汇后台监听 message,收到消息(data.data),判断来源(data.origin)是否为昆仑后台,存储到 localStorage 中
window.addEventListener('message', token => {}, false);
-
以服务的方式分别打开两个html文件,修改各自的 origin、targetOrigin 后,kunlun 页面点击发送按钮,linhui 页面查看是否刷新显示了token
注意
- 如果是方案二,就不能使用 sessionStorage,iframe内的 sessionStorage 和 窗口内的 sessionStorage 不会共享
- 邻汇吧还要存储版本号,如果不一致会跳出登录,所以要对这部分进行处理
linhui.html
<body>
<h2>邻汇后台接收token 方案二</h2>
<div>
token:
<span class="token"></span>
</div>
<script>
let origin = 'http://localhost:7788/linhui2.html'
let targetOrigin = 'http://localhost:7788/kunlun.html';
// 监听 昆仑后台通过 iframe.contentWindow.postMessage 发送的消息
window.addEventListener('message', data => {
let token = data.data
console.log('message', token);
// 如果是方案二,就不能使用 sessionStorage,iframe内的 sessionStorage 和 窗口内的 sessionStorage 不会共享
localStorage.setItem('token', token);
}, targetOrigin)
// 以下只是轮询更新页面上调试用显示的token,和方案无关
setInterval(() => {
fn();
}, 1000);
function fn() {
let token = localStorage.getItem('token');
document.querySelector('.token').innerText = token;
// console.log('设置token');
}
</script>
</body>
kunlun.html
<body>
<h2>昆仑后台发送token</h2>
<div>
token:
<span class="token"></span>
</div>
<button onclick="sendToken()">方案二,主动发送token</button>
<iframe class="iframe" src="" frameborder="0"></iframe>
<script>
// https://www.cnblogs.com/congxueda/p/11825820.html
let origin = 'http://localhost:7788/kunlun2.html'
let targetOrigin = 'http://localhost:7788/linhui2.html';
let iframe = document.querySelector('iframe');
if (iframe) {
iframe.src = targetOrigin;
}
let token = null;
function createToken() {
token = Math.floor(Math.random() * 10000000);
return token;
}
// 方案二
function sendToken() {
token = createToken();
iframe.contentWindow.postMessage(token, targetOrigin)
console.log('sendToken');
}
// 以下只是轮询更新页面上调试用显示的token,和方案无关
setInterval(() => {
fn();
}, 1000);
function fn() {
let token = localStorage.getItem('token');
document.querySelector('.token').innerText = token;
// console.log('设置token');
}
</script>
</body>