Skip to content

GitLab

  • Projects
  • Groups
  • Snippets
  • Help
    • Loading...
  • Help
    • Help
    • Support
    • Community forum
    • Submit feedback
    • Contribute to GitLab
  • Sign in / Register
T treasure
  • Project overview
    • Project overview
    • Details
    • Activity
    • Releases
  • Repository
    • Repository
    • Files
    • Commits
    • Branches
    • Tags
    • Contributors
    • Graph
    • Compare
  • Issues 12
    • Issues 12
    • List
    • Boards
    • Labels
    • Service Desk
    • Milestones
  • Merge requests 0
    • Merge requests 0
  • CI/CD
    • CI/CD
    • Pipelines
    • Jobs
    • Schedules
  • Operations
    • Operations
    • Incidents
    • Environments
  • Packages & Registries
    • Packages & Registries
    • Container Registry
  • Analytics
    • Analytics
    • CI/CD
    • Repository
    • Value Stream
  • Wiki
    • Wiki
  • External wiki
    • External wiki
  • Snippets
    • Snippets
  • Members
    • Members
  • Activity
  • Graph
  • Create a new issue
  • Jobs
  • Commits
  • Issue Boards
Collapse sidebar
  • FE
  • treasure
  • Issues
  • #10

Closed
Open
Created Sep 16, 2020 by 曹瑞秋@rachelDeveloper

基于Iframe+Postmessage前端实现SSO(SingleSignOn)单点登录功能

概述

  • SSO单点登录前端需要实现的主要功能就是一端token,多端可用 ~
  • 同时后端基于Oauth2+JWT实现的SSO单点登录功能需要有统一的中转登录页 middle.html
  • 触发的场景可以有从中转登录页登录后,通过昆仑跳转只邻汇吧、PMS、location;或者直接通过新窗口打开这些页面

middle.html代码如下

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>登录验证中心</title>
  <style>
    .login-form{
      margin: 100px auto;
      text-align: center;
    }
    .login-form button{
      width: 200px;
      margin-top: 20px;;
    }
    .login-form p{
      margin-top: 20px;
      font-size: 24px;;
    }
    .login-form p.success{
      color: lightgreen;
    }
    .login-form p.fail{
      color: orangered;
    }
  </style>
</head>
<body>
  <form class="login-form">
    <div>
      <label for="username">账号: </label><input type="text" name="username" id="username"/>
    </div>
    <div>
      <label for="password">密码: </label><input type="password" name="password" id="password"/>
    </div>
    <button type="button" onclick="sendLogin('http://pms.nps.700777.xyz/api/oauth/token', 'post')">登录</button>
    <p class="login-info"></p>
  </form>
  <div class="nav">
    <a href="http://127.0.0.1:5500/client1.html">client1</a>
    <a href="http://127.0.0.1:5500/client2.html">client2</a>
  </div>
  <script>
    const $ = name => document.querySelector(name);
    // 发送登陆请求
    function sendLogin(url, method) {
      const username = $("#username");
      const password = $("#password");
      const loginInfo = $(".login-info");
      // 服务端需要的数据
      let grantType = "password";
      let clientId = 2;
      let clientSecret = "FSVgqgds7HXyf1tT1wFhkyMlNUoiSsCPZMS9xAkr";
      let requestParame = `grant_type=${grantType}&client_id=${clientId}&client_secret=${clientSecret}&scope=*&username=${username.value}&password=${password.value}`
      const xhr = new XMLHttpRequest();
      xhr.open(method, url, true);
      xhr.setRequestHeader("content-type","application/x-www-form-urlencoded");
      xhr.send(requestParame);
      xhr.onreadystatechange = function() {
        if (xhr.readyState === 4 && xhr.status === 200) {
          let data = JSON.parse(xhr.responseText);
          // 将token通过localStorage保存在本地
          let token = `${data.token_type} ${data.access_token}`;
          localStorageMap.setItem("token", token);
          loginInfo.innerText = "登录成功";
          loginInfo.className = "login-info success";
          if (getQueryOriginUrl()) {
            // 若有来源则跳转到来源页
            setTimeout(function() {
              window.location.href = getQueryOriginUrl();
            }, 500)
          }
        }
        else {
          loginInfo.innerText = "登录失败";
          loginInfo.className = "login-info fail";
        }
      }
    }

    // 获取地址栏参数(跳转数据来源)
    function getQueryOriginUrl() {
       var query = window.location.search.substring(1);
       var originUrl = query.split("=")[1];
       return decodeURIComponent(originUrl);
    }

    // localStorage映射关系
    let localStorageMap = {
      setItem: (key, value) => localStorage.setItem(key, value),
      getItem: (key) => localStorage.getItem(key),
      removeItem: (key) => localStorage.removeItem(key)
    }
    // “中转页面”监听 ifameWin.postMessage() 事件
    window.addEventListener('message', function (e) {
      let { method, key, value, origin } = e.data
      // 处理对应的存储方法
      let result = localStorageMap[method](key, value)
      // 返回给当前 client 的数据
      let response = {
        result,
      }
      // 验证是否为信任域
      function receiveMessage($origin) {
        switch ($origin) {
          case "linhuiba":
          case "PMS":
          case "location":
            return $origin;
          default:
            return false;      
        }
      }
      // 把获取的数据,传递给 client 窗口
      window.parent.postMessage(response, origin)
    })
  </script>
</body>
</html>

需要获取登录token信息的client1.html代码如下

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>客户端1-跨域获取localStorage</title>
  <style>
    .login-status{
      margin-top: 50px;
      text-align: center;
      font-size: 24px;
    }
    .login-status.success{
      color: lightgreen;
    }
    .login-status.fail{
      color: orangered;
    }
  </style>
</head>
<body>
  <!-- 开始存储事件 -->

  <!-- 此次为模拟获取中转页token -->
  <button onclick="handleSetLocalStorage('getItem', 'token', 'http://127.0.0.1:5500')">Log In</button>
  <button onclick="handleSetLocalStorage('removeItem', 'token', 'http://127.0.0.1:5500')">Log Out</button>
  <!-- iframe 嵌套“中转页面” middle.html -->
  <iframe src="http://pms.nps.700777.xyz/" frameborder="0" id="middle" hidden></iframe>
  <p class="login-status">登录状态</p>
  <script>
    const $ = name => document.querySelector(name);
    // 获取 iframe window 对象
    const ifameWin = $('#middle').contentWindow;
    // 获取登录状态对象
    const loginStatus = $('.login-status');
    // 登录中心地址
    const loginCenterUrl = 'http://pms.nps.700777.xyz/';

    function handleSetLocalStorage (method, key, origin, value="") {
      let request = {
        // 存储的方法
        method,
        // 存储的 key
        key,
        // 设置当前域
        origin,
        // 存储的 value
        value
      }
      // 向 iframe “中转页面”发送消息
      ifameWin.postMessage(request, loginCenterUrl)
    }
    window.addEventListener('message', function(e) {
      console.log('我收到了', e.data);
      // 设置token到当前域localStorage中
      localStorage.setItem('token', e.data.result);
      if (!e.data.result) {
        loginStatus.innerText = "未登录,即将跳转至登录中心";
        loginStatus.className = "login-status fail";
        setTimeout(function() {
          window.location.href = `${loginCenterUrl}?from=${encodeURIComponent('http://127.0.0.1:5500/client1.html')}`;
        }, 1500);
      }
      else {
        loginStatus.innerText = "已登录";
        loginStatus.className = "login-status success";
      }
    })
  </script>
</body>
</html>

需要获取登录token信息的client2.html代码如下

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>客户端2-跨域获取localStorage</title>
</head>
<body>
  <!-- 获取本地存储数据 -->
  <button onclick="handleGetItem()">client2-getItem</button>
  <!-- iframe 嵌套“中转页面” middle.html -->
  <iframe src="http://pms.nps.700777.xyz/" frameborder="0" id="middle" hidden></iframe>

  <script>
    const $ = name => document.querySelector(name);
    // 获取 iframe window 对象
    const ifameWin = $('#middle').contentWindow;

    // 登录中心地址
    const loginCenterUrl = 'http://pms.nps.700777.xyz/';

    function handleSetLocalStorage (method, key, origin, value="") {
      let request = {
        // 存储的方法
        method,
        // 存储的 key
        key,
        // 设置当前域
        origin,
        // 存储的 value
        value
      }
      // 向 iframe “中转页面”发送消息
      ifameWin.postMessage(request, loginCenterUrl)
    }

    // 监听 iframe “中转页面”返回的消息
    window.addEventListener('message', function (e) {
      console.log('client 2 获取到数据啦:', e.data.result);
      // 设置token到当前域localStorage中
      localStorage.setItem('token', e.data.result);
    })
  </script>
</body>
</html>
Assignee
Assign to
None
Milestone
None
Assign milestone
Time tracking