說明
Q: 這個工具用來做什麼的呢
A: 使用者有不同的許可權,比如管理員,vip,普通使用者,每個使用者對應訪問api,頁面都不一樣
nodejs有兩個比較有名的許可權管理模組 一個是acl 一個是rbac 綜合對比了一下最終在做專案的時候選擇了acl
使用方法
- 建立起配置檔案
- 使用者登入後分配相應的許可權
- 需要控制的地方使用acl做校檢
配置檔案
const Acl = require(`acl`);
const aclConfig = require(`../conf/acl_conf`);
module.exports = function (app, express) {
const acl = new Acl(new Acl.memoryBackend()); // eslint-disable-line
acl.allow(aclConfig);
return acl;
};
// acl_conf
module.exports = [
{
roles: `normal`, // 一般使用者
allows: [
{ resources: [`/admin/reserve`], permissions: [`get`] },
]
},
{
roles: `member`, // 會員
allows: [
{ resources: [`/admin/reserve`, `/admin/sign`], permissions: [`get`] },
{ resources: [`/admin/reserve/add-visitor`, `/admin/reserve/add-visitor-excel`, `/admin/reserve/audit`, `/admin/sign/ban`], permissions: [`post`] },
]
},
{
roles: `admin`, // 管理
allows: [
{ resources: [`/admin/reserve`, `/admin/sign`, `/admin/set`], permissions: [`get`] },
{ resources: [`/admin/set/add-user`, `/admin/set/modify-user`], permissions: [`post`] },
]
},
{
roles: `root`, // 最高許可權
allows: [
{ resources: [`/admin/reserve`, `/admin/sign`, `/admin/set`], permissions: [`get`] },
]
}
];
複製程式碼
校檢
這裡是結合express做校檢…結果發現acl自己提供的中介軟體太雞肋了,這裡就重寫了一個。
function auth() {
return async function (req, res, next) {
let resource = req.baseUrl;
if (req.route) { // 正常在control中使用有route屬性 但是使用app.use則不會有
resource = resource + req.route.path;
}
console.log(`resource`, resource);
// 容錯 如果訪問的是 /admin/sign/ 後面為 /符號認定也為過
if (resource[resource.length - 1] === `/`) {
resource = resource.slice(0, -1);
}
let role = await acl.hasRole(req.session.userName, `root`);
if (role) {
return next();
}
let result = await acl.isAllowed(req.session.userName, resource, req.method.toLowerCase());
// if (!result) {
// let err = {
// errorCode: 401,
// message: `使用者未授權訪問`,
// };
// return res.status(401).send(err.message);
// }
next();
};
}
複製程式碼
有點要說明的是express.Router支援匯出一個Router模組 再在app.use使用,但是如果你這樣使用app.use(`/admin/user`,auth(), userRoute);
那麼是在auth這個函式是獲取不到req.route
這個屬性的。
因為acl對訪問許可權做的是強匹配,所以需要有一定的容錯
登入的許可權分配
result為資料庫查詢出來的使用者資訊,或者後臺api返給的使用者資訊,這裡的switch可以使用配置檔案的形式,因為我這邊本次專案只有三個許可權,所以就在這裡簡單寫了一下。
let roleName = `normal`;
switch (result.result.privilege) {
case 0:
roleName = `admin`;
break;
case 1:
roleName = `normal`;
break;
case 2:
roleName = `member`;
break;
}
if (result.result.name === `Nathan`) {
roleName = `root`;
}
req.session[`role`] = roleName;
// req.session[`role`] = `root`; // test
acl.addUserRoles(result.result.name, roleName);
// acl.addUserRoles(result.result.name, `root`); // test
複製程式碼
pug頁面中的渲染邏輯控制
在 express+pug中app.locals.auth= async function(){}
這個寫法在pug渲染的時候是不會得出最終結果的,因為pug是同步的,那麼我如何控制當前頁面或者說當前頁面的按鈕使用者是否有許可權展示出來, 這裡通用的做法有
- 使用者在登入的時候有一個路由表和元件表 然後在渲染的時候 根據這個表去渲染
- 在需要許可權控制的地方,使用函式來判斷使用者是否有許可權訪問
我這裡採用的是結局方案2.因為比較方便, 但是問題來了 express+pug是不支援非同步的寫法,而acl提供給我們的全是非同步的, 因為時間原因,我沒有去深究裡面的判斷,而是採用了一種耦合性比較高但是比較方便的判斷方法.
app.locals.hasRole = function (userRole, path, method = `get`) {
if (userRole === `root`) {
return true;
}
const current = aclConf.find((n) => {
return n[`roles`] === userRole;
});
let isFind = false;
for (let i of current.allows) {
const currentPath = i.resources; // 目前陣列第一個為單純的get路由
isFind = currentPath.includes(path);
if (isFind) {
// 如果找到包含該路徑 並且method也對應得上 那麼則通過
if (i.permissions.includes(method)) {
break;
}
// 如果找到該路徑 但是method對應不上 則繼續找.
continue;
}
}
return isFind;
};
複製程式碼
上述內碼表比較簡單, 去遍歷acl_conf,查詢使用者是否有當前頁面的或者按鈕的許可權 因為acl_conf在載入的時候就已經被寫入記憶體了,所以效能消耗不會特別大。比如下面的例子。
if hasRole(user.role, `/admin/reserve/audit`, `post`)
.col.l3.right-align
a.waves-effect.waves-light.btn.margin-right.blue.font12.js-reviewe-ok 同意
a.waves-effect.waves-light.btn.pink.accent-3.font12.js-reviewe-no 拒絕
複製程式碼
結尾
依靠acl這個元件可以快速打造一個使用者的許可權管理模組。 但是還有個問題 也急速那個app.locals.hasRole函式, 如果你使用removeAllow動態改變了使用者的許可權表,那麼hasRole函式就很麻煩了。 所以在這種情況下 有以下幾個解決方案
- 從acl原始碼入手
- 每次渲染的時候就把資料準備好
const hasBtn1Role = hasRole(user.role, `/xxx`,`get`);
res.render(`a.pug`,{hasBtn1Role})
複製程式碼