JavaScript之同站多域名共享Token实现方案

发布时间 2023-09-05 15:46:51作者: C+V-Engineer

背景

由于公司业务涉及到多个国家,每个国家站的访问的域名不同(指向同一个 Web 服务)
在站内能够切换不同的国家,服务端一个token支持所有国家鉴权
此时需要前端将Token等相关信息共享到即将跳转到的新站点,因为不同域,浏览器不会共享 Cookie

方案

  1. 将 Token 相关信息 通过 URL Query 参数传到新站点
    问题:
    暴露在地址栏的信息过多
    信息过多容易丢失,有概率超出地址栏最大长度限制

  2. 增加一个静态的中转页文件(如: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>
  1. postMessage + iframe方案,解决数据量大,及地址栏暴露信息问题
// 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 参数足以)