在項目中,權(quán)限驗證與安全性非常重要,是一開始就必須考慮和搭建的基礎(chǔ)核心功能。我們所要做到的是:不同的權(quán)限對應著不同的路由,同時側(cè)邊欄也需根據(jù)不同的權(quán)限,異步生成。
思路
這里參考了vue-admin項目的代碼:vue-element-admin權(quán)限驗證篇,但是本項目的業(yè)務(wù)邏輯需求是每個頁面的權(quán)限是動態(tài)配置的,而vue-admin項目中是寫死預設(shè)的,不完全符合需求,故進行了改進(過去的一些項目中沒用addRoute,權(quán)限控制代碼里很多都是各種if/else的邏輯判斷,代碼相當?shù)鸟詈虾蛷碗s,維護起來相當?shù)睦щy,在vue2.2.0以后新增了router.addRoutes,就可相對方便的做權(quán)限控制了)。
前端會有一份公共的路由表,它表示了不需要權(quán)限,公共可訪問的權(quán)限,如登錄頁面、主面板等,同時存儲一份會根據(jù)權(quán)限變動的路由表,當用戶登錄之后,通過 token 獲取用戶的 role ,同時后端根據(jù)角色返回用戶可以訪問的菜單及內(nèi)容信息,最后前端動態(tài)算出其對應有權(quán)限的路由,再通過router.addRoutes動態(tài)掛載路由。
但這些控制都只是頁面級的,后端則會驗證每一個涉及請求的操作,驗證其是否有該操作的權(quán)限,每一個后臺的請求不管是 get 還是 post 都會讓前端在請求 header里面攜帶用戶的 token,后端會根據(jù)該 token 來驗證用戶是否有權(quán)限執(zhí)行該操作。若沒有權(quán)限則拋出一個對應的狀態(tài)碼,前端檢測到該狀態(tài)碼,做出相對應的操作。
實現(xiàn)步驟
- 創(chuàng)建vue實例的時候?qū)ue-router掛載,但這個時候vue-router掛載一些登錄或者不用權(quán)限的公用的頁面。
- 當用戶登錄后,獲取用戶可訪問的菜單name等信息,將信息和路由表每個頁面的需要的權(quán)限作比較,生成最終用戶可訪問的路由表。
- 調(diào)用router.addRoutes(store.getters.addRouters)添加用戶可訪問的路由。
使用vuex管理路由表,根據(jù)vuex中可訪問的路由渲染側(cè)邊欄組件。
前端存儲的權(quán)限路由表:
const clientAsyncRoutes = [
{
path: "/form",
component: "Layout",
name: "form",
children: [
{
path: "index",
name: "form-1",
component: () => import("@/views/form/index"),
meta: { title: "Form", icon: "form" }
}
]
},
{
path: "/form2",
component: () => import("@/views/form2/index"),
name: "form2"
},
{
path: "/nested",
component: "Layout",
redirect: "/nested/menu1",
name: "nested",
meta: {
title: "Nested權(quán)限測試",
icon: "nested"
},
children: [
{
path: "menu1",
component: () => import("@/views/nested/menu1/index"),
name: "nested-1",
meta: { title: "Menu1" },
children: [
{
path: "menu1-1",
component: () => import("@/views/nested/menu1/menu1-1"),
name: "nested-1-1",
meta: { title: "Menu1-1" }
},
{
path: "menu1-2",
component: () => import("@/views/nested/menu1/menu1-2"),
name: "nested-1-2",
meta: { title: "Menu1-2" },
children: [
{
path: "menu1-2-1",
component: () =>
import("@/views/nested/menu1/menu1-2/menu1-2-1"),
name: "nested-1-2-1",
meta: { title: "Menu1-2-1" }
},
{
path: "menu1-2-2",
component: () =>
import("@/views/nested/menu1/menu1-2/menu1-2-2"),
name: "nested-1-2-2",
meta: { title: "Menu1-2-2" }
}
]
}
]
},
{
path: "menu2",
name: "nested-2",
component: () => import("@/views/nested/menu2/index"),
meta: { title: "menu2" }
}
]
}
];
登陸時后端返回的可訪問菜單數(shù)據(jù):
const serverRouter = [
{
name: "form",
children: [{ name: "form-1", content: ["aa", "bb"] }]
},
{
name: "form2",
},
{
name: "nested",
children: [
{
name: "nested-1",
children: [
{ name: "nested-1-1" },
{
name: "nested-1-2",
children: [{ name: "nested-1-2-2", content: ["c", "d"] }]
}
]
},
]
}
];
前端對數(shù)據(jù)進行遍歷和重組,最終遞歸生成動態(tài)路由數(shù)據(jù):
//生成路由表部分代碼
function makePermissionRouters(serverRouter, clientAsyncRoutes) {
const res = [];
clientAsyncRoutes.map(ele => {
for (let i = 0; i < serverRouter.length; i++) {
const element = serverRouter[i];
if (ele.name === element.name) {
const tmp = deepClone(ele);
if (element.content) {
tmp.meta.content = element.content;
}
if (element.children) {
tmp.children = makePermissionRouters(
element.children,
ele.children
);
}
res.push(tmp);
}
}
});
return res;
}
//生成的動態(tài)路由
const Routes = [
{
path: "/form",
component: "Layout",
name: "form",
children: [
{
path: "index",
name: "form-1",
component: () => import("@/views/form/index"),
meta: { title: "Form", icon: "form", content: ["aa", "bb"] }
}
]
},
{
path: "/form2",
component: () => import("@/views/form2/index"),
name: "form2"
},
{
path: "/nested",
component: "Layout",
redirect: "/nested/menu1",
name: "nested",
meta: {
title: "Nested權(quán)限測試",
icon: "nested"
},
children: [
{
path: "menu1",
component: () => import("@/views/nested/menu1/index"), // Parent router-view
name: "nested-1",
meta: { title: "Menu1" },
children: [
{
path: "menu1-1",
component: () => import("@/views/nested/menu1/menu1-1"),
name: "nested-1-1",
meta: { title: "Menu1-1" }
},
{
path: "menu1-2",
component: () => import("@/views/nested/menu1/menu1-2"),
name: "nested-1-2",
meta: { title: "Menu1-2" },
children: [
{
path: "menu1-2-2",
component: () =>
import("@/views/nested/menu1/menu1-2/menu1-2-2"),
name: "nested-1-2-2",
meta: { title: "Menu1-2-2", content: ["c", "d"] }
}
]
}
]
}
]
}
];
這里我根據(jù) vue-router官方推薦 的方法通過meta標簽來標示改頁面能訪問的權(quán)限有哪些。如meta: {content: ["c", "d"] }表示該頁面用戶擁有查看c和d內(nèi)容區(qū)域的權(quán)限。
若需要查看所有用戶權(quán)限,如管理員編輯角色權(quán)限分配時,后臺只需返回 每個角色可訪問的菜單數(shù)據(jù)便可,結(jié)構(gòu)同登錄時相同,如:
const data = [
{
username: "user1",
role: "admin",
route: [
{
name: "form",
children: [{ name: "form-1", content: ["aa", "bb"] }]
},
{
name: "form2"
}
]
},
{
username: "user2",
role: "editor",
route: [
{
name: "form",
children: [{ name: "form-1", content: ["aa", "bb"] }]
}
]
}
];
管理員可以用樹形圖的方式勾選編輯權(quán)限,進行用戶的權(quán)限分配。