JavaScript之同站多域名共享Token实现方案
背景
由于公司业务涉及到多个国家,每个国家站的访问的域名不同(指向同一个 Web 服务)
在站内能够切换不同的国家,服务端一个token支持所有国家鉴权
此时需要前端将Token等相关信息共享到即将跳转到的新站点,因为不同域,浏览器不会共享 Cookie
方案
-
将 Token 相关信息 通过 URL Query 参数传到新站点
问题:
暴露在地址栏的信息过多
信息过多容易丢失,有概率超出地址栏最大长度限制 -
增加一个静态的中转页文件(如:switch-region.html),将 Token 等信息通过 URL Query 参数嵌套在 iframe 中,iframe 中对Query解析并写入新域名,在跳转到新站点
解决了信息暴露在地址栏的问题,并没有根本解决 方案1 的第二点比如从 http://ke.testing.com 跳转到 http://ng.testing.com
// <!-- ke js -->
// <!-- ...... -->
// 创建一个iframe
const iframe: HTMLIFrameElement = document.createElement('iframe')
iframe.style.height = '0'
iframe.style.border = 'none'
// 先跳转到 switch-region.html 中转页,将 token 等信息带到新域名
iframe.src = `http://ng.testing.com/switch-region.html?Token=xxxxxxxTokenxxxxxxxxxx`
document.body.appendChild(iframe)
// iframe加载,表示token已经写入到新域名中
// 跳转到 ng 站点
iframe.onload = () => {
setTimeout(() => {
window.location.href = 'http://ng.testing.com'
}, 200)
}
<!-- ...... -->
<!-- ng -->
<!-- switch-region.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title></title>
<style></style>
<script>
function getUrlParam(name){
var url = window.location.search; //获取url中"?"符后的字串
var theRequest = new Object();
if (url.indexOf("?") != -1) {
var str = url.substr(1);
strs = str.split("&");
for(var i = 0; i < strs.length; i ++) {
theRequest[strs[i].split("=")[0]]=decodeURI(strs[i].split("=")[1]);
}
}
return theRequest;
}
function setCookie(cName, cValue, exDays){
var d = new Date();
d.setTime(d.getTime()+(exDays*24*60*60*1000));
var expires = "expires="+d.toGMTString();
document.cookie = cName + "=" + cValue + "; " + expires;
}
// 比如获取到url中的 Token,写入新域名的cookie中
setCookie('Token', getUrlParams('Token'))
</script>
</head>
<body>
</body>
</html>
- postMessage + iframe方案,解决数据量大,及地址栏暴露信息问题
注意:不同主域名下,由于浏览器同源策略可能会导致Cookie写入失败
- 可以通过打开新页签解决
- SSR 框架可以通过服务端API来写入Cookie解决
- 子域名(a.xxx.com / b.xxx.com),可以通过 docment.doamin = "b.xxx.com" 解决
Tips: 当然还有很多方法,比如 代理服务器、CORS策略 等
// ke js
const iframe = document.createElement('iframe')
iframe.style.height = '0'
iframe.style.border = 'none'
iframe.src = `http://ng.testing.com/switch-region.html`
document.body.appendChild(iframe)
const token = 'xxxxxxxTokenxxxxxxxxxx'
iframe.onload = () => {
// 新域名加载完成后发送一个 事件
iframe.contentWindow?.postMessage(`userinfo|${JSON.stringify({ Token })}`, '*')
}
// 监听 switch-region.htlm 的回调
window.addEventListener('message', (event: any) => {
if(event.data === 'jump') {
console.log('---------- jump -----------')
// 跳转到新域名
window.location.href = 'http://ng.testing.com'
window.removeEventListener('message', () => {})
}
})
<!-- ng -->
<!-- switch-region.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title></title>
<style></style>
<script>
function setCookie(cName, cValue, exDays){
var d = new Date();
d.setTime(d.getTime()+(exDays*24*60*60*1000));
var expires = "expires="+d.toGMTString();
document.cookie = cName + "=" + cValue + "; " + expires;
}
window.addEventListener('message', function (event) {
// 监听上层 window 派发的事件
if(Object
.prototype
.toString
.call(event.data)
.slice(8, -1) === 'String'
) {
const [key, value] = event.data.split('|')
if(key === 'userinfo') {
setCookie('Token', value)
// 告诉上层 window,cookie写入新域名,可以开始跳转了
window.parent.postMessage('jump', '*')
window.removeEventListener('message', () => {})
}
}
})
</script>
</head>
<body>
</body>
</html>
总结
方案3能完美解决其他两个方案的不足,但是用 iframe 的方式会导致多一次加载(如果使用类似上面 switch-region.html 这种静态中转文件还是很快的),使用 postMessage 通信也会有一些时间上面的损耗(如果只需要带一个 token,用 Query 参数足以)