vue伪(微)前端开发指南:element-admin + sso + iframe 系统嵌套

最近组内开发了一套数字中台的后台管理系统,该系统作为入口系统 ,可以在系统内访问其他子系统的页面,所有的系统集成了sso 单点登录,因此子系统嵌入的页面不再需要校验登录逻辑,除非token令牌已经失效,有相关的逻辑处理.踩了一些坑,比如内嵌系统因为浏览器cookie缓存机制,需要将token令牌存储机制改为sessionStorage等等;
在这里插入图片描述

1.内嵌页面在所属系统内的资源设置(路由动态返回)

  • 归属系统在UAC(资源配置系统)单独配置内嵌页的资源,将内嵌页提到一级路由,资源src直接指向内嵌页组件对象而不是Layout,这样可以避免内嵌页面显示所属系统的布局Layout(面包屑,导航,侧边栏等);单独配置的内嵌页面资源可以设置隐藏,这样在归属系统内就不显得冲突(—如果原系统也需要维护内嵌页面资源的话),嵌入页面的按钮权限在UAC自行配置

1.1配置参考(e.g UAC配置子系统工厂配置)

1.2效果(e.g 工厂配置,子系统内访问效果)

在这里插入图片描述

1.39(嵌入园区工厂配置后 中台内访问效果)

–图片挂了 自行脑补

2.中台集成内嵌页面相关逻辑

  • 集成内嵌页面是通过封装好的iframe组件,iframe 的src资源地址指向归属系统的login页面,并且在url中拼接了redirect(登录后要跳转的页面)以及中台的token令牌,因为现在各系统都是通过sso单点登录,因此token通用,在嵌入页归属系统中只需要从url拿到token令牌,不必触发本身登录的逻辑.子系统需要在路由守卫中,有token的情况下多加一层判定当前是否是嵌入在iframe页面,如果是,清空当前token,带参跳转login页面,在login页面需要处理处理(1.存储要跳转的链接,2.localstorage存储token,3.跳转redirect页面),注意,由于cookie跨域限制,内嵌页在中台iframe里不能获取cookie里存储的token,因此建议将store存储token的相关逻辑修改为local storage存储

2.1 子系统login页面逻辑参考(只处理逻辑,不需要视图样板)

``

<script>
import { getQueryObject } from "@/utils";
export default {
  name: "Login",
  mounted() {
    const urlParams = getQueryObject();
    if (urlParams.redirect)
      localStorage.setItem("redirectUrl", urlParams.redirect);
    if (urlParams.token) {
      // sso 单点登录返回 携带token
      this.$store.dispatch("user/login", urlParams.token).then(() => {
        const redirectUrl = localStorage.getItem("redirectUrl") || "/";
        this.$router.push({
          path: redirectUrl
        });
      });
    } else {
      // 没有token 也不是sso跳转返回 => sso单点登录
      const current = `${window.location.href.split("?")[0]}`;
      window.location.href = `http://sso/login?redirect_url=${current.replace(
        "#",
        "%23"
      )}`;
    }
  },
  render(h) {
    return h();
  },
};
</script>

2.2 园区路由守卫逻辑参考(permission.js)

router.beforeEach(async (to, from, next) => {
  // start progress bar
  NProgress.start();

  // set page title
  document.title = getPageTitle(to.meta.title || "XX后台管理");
  // determine whether the user has logged in
  const hasToken = localStorage.getItem("Park-Token");
  if (hasToken) {
    if (to.path === "/login") {
      if (window.frames.length != parent.frames.length) {
        // 判定中台iframe 每次清空token 从url获取 带参跳转login
        const urlParams = getQueryObject();
        await store.dispatch("user/resetToken");
        next(`/login?token=${urlParams.token}&redirect=${urlParams.redirect}`);
        NProgress.done();
      } else {
         // if is logged in, redirect to the home page
        next({
          path: "/"
        });
        NProgress.done();
      }
    } else {
      // determine whether the user has obtained his permission roles through getInfo
      const hasRoles = store.getters.roles && store.getters.roles.length > 0;
      if (hasRoles) {
        next();
      } else {
        try {
          // get user info
          //TODO roles
          const resourceList = await store.dispatch("user/getInfo");
          // 生成路由,引入组件
          const nestedRouter = filterAsyncRouter(
            resourceList.sort(function(a, b) {
              // 根据sort 排序
              return a.sort - b.sort;
            })
          );

          // 添加未授权路由
          nestedRouter.push({
            path: "*",
            redirect: "/404"
          });
          // 动态添加可访问路由
          const accessRoutes = await store.dispatch(
            "permission/generateRoutes",
            nestedRouter,
            ["basic"]
          );

          router.addRoutes(accessRoutes);
          next({
            ...to,
            replace: true
          });
        } catch (error) {
          // remove token and go to login page to re-login
          // 移除令牌并转到登录页以重新登录
          await store.dispatch("user/resetToken");
          Message.error(error || "Has Error");
          next(`/login?redirect=${to.path}`);
          NProgress.done();
        }
      }
    }
  } else {
    /* has no token*/
    if (whiteList.indexOf(to.path) !== -1) {
      // in the free login whitelist, go directly
      next();
    } else {
      // 没有访问权限的其他页将重定向到登录页。
      next(`/login?redirect=${to.path}`);
      NProgress.done();
    }
  }
});

2.3 园区login存储token逻辑参考(store/modules/user)

const state = {
  token: localStorage.getItem("Park-Token") || "",
  name: "",
  code: "",
  avatar: "",
  introduction: "",
  roles: [],
  party: "",
  secondUploadDetail: -1,
  driver: {},
  resourcelist: {},
  translatelist: {},
  branch: {}
};

const mutations = {
  SET_TOKEN: (state, token) => {
    state.token = token;
    localStorage.setItem("Park-Token", token);
  },
}

3.内嵌页面token失效处理

  • 内嵌页面归属系统 token失效请求失败,axios 拦截器会弹出提示,此时,证明外层中台的token失效了,在MessageBox的确认回调中,多加一层判断,判定当前是否嵌入在iframe中,是的话不要走归属系统的sso跳转,先清空本地存储的token以及redirect,通过postmessage向iframe外层通信,传值{code:700},让中台处理token失效的逻辑,

3.1 axios 拦截器处理token失效逻辑参考(utils/service.js)

     if (res.code === 700)
        return MessageBox.confirm("登录令牌失效,请重新登录!", "提示", {
          confirmButtonText: "登录",
          cancelButtonText: "取消",
          type: "warning",
          center: true
        }).then(() => {
          if (window.frames.length != parent.frames.length) {
            // 数字中台iframe嵌套通信  => 让中台处理登陆
            store.dispatch("user/resetToken").then(() => {
              window.parent.postMessage(
                {
                  code: "700"
                },
                "*"
              );
            });
          } else {
            store.dispatch("user/resetToken").then(() => {
              const currentPath = `${
                window.location.href.split("#")[1].split("?")[0]
              }`;
              router.push(`/login?redirect=${currentPath}`);
            });
          }
        });

4.中台权限资源配置

  • 以园区系统为例,中台嵌入了园区系统的园区配置及其下三个子模块,工厂配置,排队类型配置,作业类型配置这三个模块 ,按照本指南第一步👆配置完成后,在中台的配置如图二,如果嵌入页面有父级,那就指向一个统一的路由视图(仅用于包裹),如果没有,就直接指向一个统一的处理嵌入页的页面.其中,资源标识,即代表嵌入页面要跳转的redirect路径;外部资源,代表子系统的访问地址.这些配置都由中台开发人员配置,但需要子系统提供嵌入页面的配置数据,即资源标识=>用于redirect,资源名称=>用于配置路由名称,外部资源=>配置子系统访问地址

​ (图1)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qGycmXAv-1624673966249)(/Users/deep1nblur/Library/Application Support/typora-user-images/image-20210609192855859.png)]

​ (图二)

  • 中台iframe组件代码参考

<template>
  <div>
    <IframeBar :src="src" :key="$route.path" />
  </div>
</template>
<script>
import IframeBar from "@/components/Iframe";
import { getToken } from "@/utils/auth";

export default {
  components: { IframeBar },
  data() {
    return {
      src: "",
    };
  },
  methods: {
    handleMessage(event) {
      // 处理 iframe 通信
      // console.log("===iframe组件通信", event);
      if (event.data && event.data.code == "700") {
        // iframe 内嵌页面  token失效了
        this.$store.dispatch("user/resetToken").then(() => {
          const current = `${window.location.href.split("?")[0]}`;
          window.location.href = `http://sso/login?redirect_url=${current.replace(
            "#",
            "%23"
          )}`;
        });
      }
    },
  },
  mounted() {
    this.$store.dispatch("app/toogleLoading", true);
  },
  created() {
    // 赋值 外部资源路径
    if (!this.$route.meta.pluginPath)
      return this.$message({
        message: "UAC未配置外部资源路径!",
        type: "warning",
      });
    this.src = `${this.$route.meta.pluginPath}?redirect=${
      this.$route.meta.title
    }&token=${getToken()}`;
    window.addEventListener("message", this.handleMessage, false);
  },
  destroy() {
    this.$store.dispatch("app/toogleLoading", false);
    window.removeEventListener("message", this.handleMessage);
  },
};
</script>

欢迎交流 私信

评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值