Role Permissions
Role permissions, in simple terms, are which pages of the system can be seen by the logged-in user and which pages of the system cannot be seen. Generally, it is the background management system that involves such complex role permissions.
For vue
technology stacks, there are generally two ways to implement role permissions.
The first is to use beforeEach
the global pre-guard.
The second is the utilization addRoutes
method.
Let's look at the first
Real-time monitoring of user permissions through beforeEach
The core of this solution is to first routes
define the routing permissions in advance, and then beforeEach
make permission logic judgments in the global pre-guard. See if the user's role roles
matches the one we configured in the route. If it matches, it is allowed to enter, and if it does not match, it will redirect to the prompt page without permission.
define routes
First of all, we define the routing of the system. For non-homepages, we generally use routing lazy loading.
In meta
it, we can define the metadata we need, here we need to add the role of our routing roles
. That is to say, the role permission that the user who enters the route needs to have. If it is not defined, it means that any role can enter.
// router/routes.js
import Home from "../views/Home.vue";
const routes = [
{
path: "/",
name: "Home",
component: Home,
meta: {
needLogin: false, // 不需要登录
title: "首页",
},
},
{
path: "/about",
name: "About",
component: () =>
import(/* webpackChunkName: "about" */ "../views/About.vue"), // 路由懒加载
meta: {
needLogin: true, // 需要登录
title: "关于",
roles: ["admin", "manage"], // 该页面只有admin和普通管理员才能进入
},
},
{
path: "/nopermission", // 没权限就进入该页面
name: "NoPermission",
component: () =>
import(
/* webpackChunkName: "nopermission" */ "../views/NoPermission.vue"
), // 路由懒加载
meta: {
needLogin: true, // 需要登录
title: "暂无权限",
},
},
{
path: "/userlist",
name: "UserList",
component: () =>
import(/* webpackChunkName: "userlist" */ "../views/UserList.vue"), // 路由懒加载
meta: {
needLogin: true, // 需要登录
title: "用户管理",
roles: ["admin"], // 该页面只有admin才能进入
},
},
{
path: "/login",
name: "Login",
component: () =>
import(/* webpackChunkName: "login" */ "../views/Login.vue"), // 路由懒加载
meta: {
needLogin: false, // 不需要登录
title: "登录",
},
},
];
export default routes;
Instantiate Router
Then create a route, which is slightly different vue2
from vue3
when creating a route, but it is common for route authentication.
// router/index.js
// vue2 写法
import VueRouter from "vue-router";
import routes from "./routes"
const router = new VueRouter({
mode: "history",
base: process.env.BASE_URL,
routes,
});
// vue3 写法
import { createRouter, createWebHistory } from "vue-router";
import routes from "./routes"
const router = createRouter({
history: createWebHistory(process.env.BASE_URL),
routes,
});
Define routing interception authentication logic
After creating the route, we can define the logic of route interception, mainly through the beforeEach
global pre-guard. Because as long as the page jumps, it will enter beforeEach
the global front guard.
The core logic here is to judge whether the page you are going to needs to log in. If you need to log in, you can further judge whether the current system has a token. If there is no token, you will be redirected to the login page.
If there is a token, further judge whether there is user information, and if there is no user information, obtain user information
After having the user information, judge whether the roles required to enter the page match the roles in the user information. If they match, they will enter the page. If they do not match, they will enter the system's unauthorized prompt page.
// router/index.js
// vue2和vue3通用
router.beforeEach(async (to, from, next) => {
// 如果需要登录
if (to.meta.needLogin) {
// 获取token
const token = localStorage.getItem("token");
// 如果有token 则直接放行
if (token) {
// 获取用户信息,从store里面获取
let userInfo = store.getters["getUserInfo"];
// 如果没有用户信息就获取用户信息
if (!userInfo) {
userInfo = await store.dispatch("getUserInfoAction");
}
// 如果页面需要权限,并且用户角色不满足则去无权限提示页
if (to.meta.roles && !to.meta.roles.includes(userInfo.role)) {
return next("/nopermission");
}
next();
} else {
// 否则去登录页
next("/login");
}
} else {
// 不需要登录则直接放行
next();
}
});
// 修改标题的工作可以放在全局后置守卫
router.afterEach((to, from) => {
if (to.meta.title) {
document.title = to.meta.title;
}
});
store
Let's look at the logic again
import { createStore } from "vuex";
export default createStore({
state: {
userInfo: null,
},
getters: {
getUserInfo: (state) => state.userInfo,
},
mutations: {
setUserInfo(state, payload) {
state.userInfo = payload;
},
},
actions: {
async getUserInfoAction({ commit }) {
// 模拟后端获取用户信息的api
const getUserInfoApi = () => {
return Promise.resolve({ role: "manage", name: "jack" }); // 假设角色为 manage
};
const userInfo = await getUserInfoApi();
commit("setUserInfo", userInfo);
return userInfo;
},
},
});
use
After the route is created, it needs to be main.js
imported and used
import router from "./router";
// vue2写法
new Vue({
router,
render: (h) => h(App),
}).$mount("#app");
// vue3写法
const app = createApp(App);
app.use(router).mount("#app");
If you are not logged in, both the go about
and userlist
the page will redirect to the login page.
After adding the simulated login to the local area token
, because the user information role is , it is no problem manage
to go , and it will be redirected to the page without permission.about
userlist
Summarize
advantage:
The implementation is simple, and the routing permissions are configured in meta
it roles
. User information only needs to contain the role of the current user.
shortcoming:
The roles owned by the system must be known in advance, and then configured in the form of dead code routes
, and dynamic addition of roles is not supported . A system suitable for small character fixation.
Dynamically add routes through addRoutes
The core of this solution is to call the back-end interface, return the menu owned by the current user role, format it into routes, and addRoutes
dynamically add these routes to the system.
define routes
Because the route is obtained from the backend, here we only need to define the basic general route, that is, the route that does not involve permissions. Such as login page, no permission page, home page.
import Home from "../views/Home.vue";
const routes = [
{
path: "/",
name: "Home",
component: Home,
meta: {
title: "首页",
},
},
{
path: "/nopermission", // 没权限就进入该页面
name: "NoPermission",
component: () =>
import(
/* webpackChunkName: "nopermission" */ "../views/NoPermission.vue"
), // 路由懒加载
meta: {
title: "暂无权限",
},
},
{
path: "/login",
name: "Login",
component: () =>
import(/* webpackChunkName: "login" */ "../views/Login.vue"), // 路由懒加载
meta: {
title: "登录",
},
},
];
export default routes;
Instantiate Router
Then create a route, vue2
which vue3
is slightly different from when creating a route.
// router/index.js
// vue2 写法
import VueRouter from "vue-router";
import routes from "./routes"
const router = new VueRouter({
mode: "history",
base: process.env.BASE_URL,
routes,
});
// vue3 写法
import { createRouter, createWebHistory } from "vue-router";
import routes from "./routes"
const router = createRouter({
history: createWebHistory(process.env.BASE_URL),
routes,
});
Define routing interception authentication logic
After creating the route, we can define the logic of dynamically adding routes.
The core logic here is to judge whether the page you are going to is in the whitelist. If it is not in the whitelist, it will further judge whether the current system has a token. If there is no token, it will redirect to the login page.
If there is a token, further judge whether there is user menu information, and if there is no user menu information, obtain user menu information
After having the user menu information, first convert the menu information into the format required by the front-end routing, and then dynamically add the route to the system through the addRoutes method.
When the user goes to a route that does not exist, the name attribute of the route will be
undefined
, so it is redirected to an unauthorized prompt page
// router/index.js
// vite创建的项目 使用这种方法实现动态加载
const modules = import.meta.glob("../views/*.vue");
// 转换成 vue-router 需要的格式
const transformRoute = (menus) => {
return menus.map((menu) => {
return {
path: menu.path,
name: menu.name,
component: modules[menu.component],
meta: {
title: menu.title,
},
};
});
};
// vue3需要手动实现该方法
const addRoutes = (routes) => {
routes.forEach((route) => {
router.addRoute(route);
});
};
// 白名单页面,不需要权限
const whiteLists = ["/login", "/"];
router.beforeEach(async (to, from, next) => {
// 是否是白名单
if (!whiteLists.includes(to.path)) {
// 获取token
const token = localStorage.getItem("token");
// 如果有token
if (token) {
// 获取用户菜单信息,从store里面获取
let userMenus = store.getters["getUserMenus"];
// 如果没有用户菜单信息就获取用户菜单信息
if (!userMenus) {
userMenus = await store.dispatch("getUserMenuAction");
// 菜单转成路由
const userRoute = transformRoute(userMenus);
// 动态添加路由 vue2 和 vue3 有细微差别
// vue2
// router.addRoutes(userRoute)
// vue3
// 因为 vue3 移除了 router.addRoutes()方法,所以需要手动实现addRoutes方法。
addRoutes(userRoute);
return next({ ...to });
}
// 有name说明路由存在,否则说明没有该路由
if (to.name) {
next();
} else {
// 去无权限页面
next("/nopermission");
}
} else {
// 否则去登录页
next("/login");
}
} else {
// 是白名单则直接进入
next();
}
});
// 修改标题的工作可以放在全局后置守卫
router.afterEach((to, from) => {
if (to.meta.title) {
document.title = to.meta.title;
}
});
store
Let's look at the logic again
import { createStore } from "vuex";
export default createStore({
state: {
userMenus: null,
},
getters: {
getUserMenus: (state) => state.userMenus,
},
mutations: {
setUserMenus(state, payload) {
state.userMenus = payload;
},
},
actions: {
async getUserMenuAction({ commit }) {
// 模拟后端获取用户菜单信息api
const getUserMenuApi = () => {
// 假设只有about菜单
return Promise.resolve([
{
path: "/about",
name: "About",
component: "../views/About.vue",
title: "关于",
},
]);
};
const userMenus = await getUserMenuApi();
commit("setUserMenus", userMenus);
return userMenus;
},
},
});
use
After the route is created, it needs to be imported and used main.js
import router from "./router";
// vue2写法
new Vue({
router,
render: (h) => h(App),
}).$mount("#app");
// vue3写法
const app = createApp(App);
app.use(router).mount("#app");
In the case of not logged in, the Go about
and userlist
page will redirect to the login page.
Add it locally token
. After the simulated login is completed, because the user about
has a menu, about
it is no problem to go to the page. Going to userlist
the page will redirect you to a page without permission.
Summarize
advantage:
System roles may not be fixed, and dynamic addition of roles is supported . It is suitable for background large-scale management system.
shortcoming:
The implementation is relatively complicated and requires more back-end cooperation.
Summarize
The advantage of the first beforeEach
method of monitoring user permissions in real time is that it is simple to implement, and the routing permissions are configured meta
in roles
it. User information only needs to contain the role of the current user. The disadvantage is also obvious. The roles owned by the system must be known in advance, and then configured in the form of dead code routes
, and dynamic addition of roles is not supported . The author thinks that it is very good if it is a system with fixed roles, such as a library management system, a teacher management system, etc.
addRoutes
The second way to achieve role permissions by dynamically adding routes has the advantage that the system roles can not be fixed, and dynamic addition of roles is supported . It is very suitable for large-scale management systems in the background, and can create role assignment menus in real time.
For example, the low-code background management system I made has a series of functions such as creating menus, roles, and users in real time.