最近组内开发了一套数字中台的后台管理系统,该系统作为入口系统 ,可以在系统内访问其他子系统的页面,所有的系统集成了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)
(图二)
- 中台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>
欢迎交流 私信