Skip to content

微前端-iframe 方案下如何解决身份验证问题

项目背景

一般的小企业的应用使用单体结构即可满足需求,而大公司需要采用微服务架构来解决高并发,高流量等问题。微前端是随着服务服诞生而出现的一种前端架构思想。即分而治之,先把一个庞大复杂的应用按功能模块分成多个独立模块,每个模块之间互相独立,独立开发,独立部署。还可以把把全部子应用集成部署到一个主应用里面,主应用包含如登录验证,权限等基础模块。本文将介绍前端主应用如何通过 iframe 通信实现,子应用的身份验证等问题。详细的微服务,微前端知识请移步其他文章

通过 iframe 通信机制传递信息

在我们项目里面微前端实现方案采用最原始的 iframe 嵌套,微前端应用最大的问题就是主应用和子应用,子应用和子应用之间的消息通信问题了。然而 iframe 提供了通信的 api。

简易代码如下:

父页面(发送消息):

js
<iframe id="myiframe" src="iframe_message.html"></iframe>
<script>
  window.onload = function() {
    var iframeWindow = document.getElementById('myiframe').contentWindow;
    iframeWindow.postMessage('Hello, I am the parent', '*');
  };
</script>

子页面(接收消息):

js
<script>
  window.addEventListener('message', function(event) {
    if (event.origin !== window.location.origin) return;
    console.log(event.data); // 输出:'Hello, I am the parent'
  }, false);
</script>

项目代码如下: 父页面(发送消息):

js
// fs框架初始化
if (topic === "fsAppMounted") {
  const msg = {
    topic: "fsAppMounted",
    uid: uid,
    from: "koca",
    body: {
      userInfo: {
        ...userInfo,
        orgCode: "999"
      },
      loginToken: token,
      menuArr: newMenuArr,
      routeTo
    }
  };
  // console.log(newMenuArr);
  // 确保iframe能够收到消息
  setTimeout(() => {
    targetIframe.postMessage(msg, "*");
    localStorage.setItem(routeToKey, "");
  }, 1);
  // syncDataFn(targetIframe, uid);
}

子页面(接收消息):

js
eventListen("message").mount((event) => {
  if (fsAppMountedResponse.userInfo) {
    // console.log('fsAppMountedResponse', fsAppMountedResponse)
    const userInfo = fsAppMountedResponse.userInfo;
    isRequest = false;
    koca.user = userInfo || {};
    // 用户信息必要字段F_OP_USER、F_OP_BRANCH、loginName、USER_TICKET_INFO等
    koca.user.F_OP_USER = userInfo.userCode || "8888";
    koca.user.F_OP_BRANCH = userInfo.orgCode || "";
    koca.user.USER_TICKET_INFO = userInfo.token || "";
    // koca.user.loginName = userInfo.loginName || ''
    koca.user.F_OP_POST = userInfo.post || "8888";
    koca.user.CURRENT_POST = userInfo.post || "8888";
    koca.user.F_OP_ROLE = userInfo.roleCode || "2";
    koca.user.F_CHANNEL = userInfo.channel || "0";
    koca.user.F_OP_SITE = userInfo.site || "";
    koca.user.F_FUNCTION = userInfo.function || "";
    koca.user.F_RUNTIME = userInfo.runtime || "";

    setLoginDataSession(JSON.stringify(koca.user));
    const loginToken = userInfo.loginToken || fsAppMountedResponse.loginToken;
    if (loginToken) {
      setLoginTokenSession(loginToken);
    }

    handleCallback();
  }

  if (isRequest && koca.user) {
    const msg = {
      topic: "fsAppMounted",
      uid: uid,
      from: "fs",
      body: {
        userInfo: koca.user
      }
    };
    targetIframe.postMessage(msg, "*");
  }
  return;
});

由上述项目代码可知,子应用等登录 SESSIONID,token 等登录所需要的凭证信息是由主应用通过 iframe 通信传递来的,然后使用 sessionStorage,localStorage 存储 token,使用 cookie 存储 SESSIONID。在以后所有接口请求头的 cookie 会自动携带 SESSIONID,在 axios 请求拦截钩子里面给需要 token 凭证的接口,在请求头里面添加 authorization=token。手动设置 token 的代码如下:

js
createHttp() {
    this._axios = axios.create({ ...requsetBase, timeout: this._conf.timeout })
    this._axios.interceptors.request.use(
      (config) => {
        config = initRequestHeadersAuthorizationInConfig(config)
        return config
      },
      (error) => {
        return Promise.reject(error)
      }
    )
    this._axios.interceptors.response.use(
      (response) => {
        setLoginTokenInResponse(response)

        return formatResponse(response, this._conf)
      },
      (error) => {
        return formatError(error, this._conf)
      }
    )
  }
initRequestHeadersAuthorizationInConfig = (config) => {
  const { httpData = {}, headers = {} } = config
  const { encryptKey = '/session/encryptKey' } = getUrlApi()
  const { login = '/auth/login' } = getUrlApi()
  const { dynamicKey = '/auth/dynamicKey' } = getUrlApi()
  const loginToken = getLoginTokenSession()

  // 使用jwt登录验证携带Authorization请求头
  if (
    httpData.source !== login &&
    httpData.source !== encryptKey &&
    httpData.source !== dynamicKey &&
    loginToken
  ) {
    headers['Authorization'] = `Bearer ${loginToken}`
  }
  return config
}

如图所示是客户端浏览器存储的 token 和 cookie,端口 3100 是主应用,8080 端口的是子应用

如图所示是接口的请求头信息

在了解这几个概念之前我们先知道认证,授权,凭证的概念

认证,授权,凭证

一. 认证(Authentication)

  • 通俗地讲就是验证当前用户的身份,证明“你是你自己”(比如:你每天上下班打卡,都需要通过指纹打卡,当你的指纹和系统里录入的指纹相匹配时,就打卡成功)
  • 互联网中常见的认证方式
    1. 1. 用户名密码登录
      2. 邮箱发送登录链接
      3. 手机号接收验证码
      4. 指纹,人脸识别

    二. 授权(Authorization)

  • 应用授予用户具有哪些权限,如查看信息权限和操作操作权限
  • 实现授权的方式有:cookie、session、token、OAuth
  • 三. 凭证(Credentials)

  • 实现认证和授权的前提是需要一种媒介(证书) 来标记访问者的身份
    1. 1、在现实生活中,每个人都会有一张专属的居民身份证,是用于证明持有人身份的一种法定证件。通过身份证,我们可以办理手机卡/银行卡/个人贷款/交通出行等等,这就是认证的凭证。
      2、在互联网应用中,服务器会给用户使用的浏览器颁发一个令牌(token),这个令牌用来表明你的身份,每次浏览器发送请求时会带上这个令牌
  • HTTP 是无状态的协议(对于事务处理没有记忆能力,每次客户端和服务端会话完成时,服务端不会保存任何会话信息):每个请求都是完全独立的,服务端无法确认当前访问者的身份信息,无法分辨上一次的请求发送者和这一次的发送者是不是同一个人。所以服务器与浏览器为了进行会话跟踪(知道是谁在访问我),就必须主动的去维护一个状态,这个状态用于告知服务端前后两个请求是否来自同一浏览器。而这个状态需要通过 cookie 或者 session 去实现。
  • cookie 存储在客户端: cookie 是服务器发送到用户浏览器并保存在本地的一小块数据,它会在浏览器下次向同一服务器再发起请求时被携带并发送到服务器上。
  • cookie 是不可跨域的: 每个 cookie 都会绑定单一的域名,无法在别的域名下获取使用,一级域名和二级域名之间是允许共享使用的(靠的是 domain)。
  • session

    session 是另一种记录服务器和客户端会话状态的机制,session 存储在服务器端,sessionId 会被存储到客户端的 cookie 中

  • session 认证流程
    1. 1、用户第一次请求服务器的时候,服务器根据用户提交的相关信息,创建对应的 Session
      2、请求返回时将此 Session 的唯一标识信息 SessionID 返回给浏览器
      3、浏览器接收到服务器返回的 SessionID 信息后,会将此信息存入到 Cookie 中,同时 Cookie 记录此 SessionID 属于哪个域名
      4、当用户第二次访问服务器的时候,请求会自动判断此域名下是否存在 Cookie 信息,如果存在自动将 Cookie 信息也发送给服务端,服务端会从 Cookie 中获取 SessionID,再根据 SessionID 查找对应的 Session 信息,如果没有找到说明用户没有登录或者登录失效,如果找到 Session 证明用户已经登录可执行后面操作。

    token(令牌)

    访问资源接口(API)时所需要的资源凭证,服务端无状态化、可扩展性好支持移动端设备,安全,支持跨程序调用,每一次请求都需要携带 token,需要把 token 放到 HTTP 的 Header 里 基于 token 的用户认证是一种服务端无状态的认证方式,服务端不用存放 token 数据。用解析 token 的计算时间换取 session 的存储空间,从而减轻服务器的压力,减少频繁的查询数据库 token 完全由应用管理,所以它可以避开同源策略 session_authorization

  • token 的身份验证流程:
  • Token 和 Session 的区别

  • Session 是一种记录服务器和客户端会话状态的机制,使服务端有状态化,可以记录会话信息。而 Token 是令牌,访问资源接口(API)时所需要的资源凭证。Token 使服务端无状态化,不会存储会话信息。
  • Session 和 Token 并不矛盾,作为身份认证 Token 安全性比 Session 好,因为每一个请求都有签名还能防止监听以及重放攻击,而 Session 就必须依赖链路层来保障通讯安全了。如果你需要实现有状态的会话,仍然可以增加 Session 来在服务器端保存一些状态。
  • 所谓 Session 认证只是简单的把 User 信息存储到 Session 里,因为 SessionID 的不可预测性,暂且认为是安全的。而 Token ,如果指的是 OAuth Token 或类似的机制的话,提供的是 认证 和 授权 ,认证是针对用户,授权是针对 App 。
  • sessionStorage 和 localStorage

    sessionStorage 和 localStorage 是浏览器用于缓存数据的 api,有个误区,就是 session 和 sessionStorage 是两个完全不一样的东西,session 是把会话信息存储到服务器,客户端通过响应头的 set-Cookie 来存储 SESSIONID,可以说是 SESSIONID 是客户端和 session 的中介链接者。而 sessionStorage 是客户端用户存储数据的 api

    结语

    本文主要讨论微前端背景下,如何在主应用处理登录逻辑之后,访问子页面无需再次登录的整体实现流程。首先要知道 iframe 之间跨域通信机制,其后要知道身份验证需要使用哪些技术。关于微前端和身份验证的深层次讨论请移步其他文章

    上次更新于: