nodejs的用戶權限管理——acl.md

acl文檔node

說明

Q: 這個工具用來作什麼的呢git

A: 用戶有不一樣的權限,好比管理員,vip,普通用戶,每一個用戶對應訪問api,頁面都不同github

nodejs有兩個比較有名的權限管理模塊 一個是acl 一個是rbac 綜合對比了一下最終在作項目的時候選擇了acl數據庫

使用方法

  1. 創建起配置文件
  2. 用戶登陸後分配相應的權限
  3. 須要控制的地方使用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本身提供的中間件太雞肋了,這裏就重寫了一個。express

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對訪問權限作的是強匹配,因此須要有必定的容錯api

登陸的權限分配

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是同步的,那麼我如何控制當前頁面或者說當前頁面的按鈕用戶是否有權限展現出來, 這裏通用的作法有bash

  1. 用戶在登陸的時候有一個路由表和組件表 而後在渲染的時候 根據這個表去渲染
  2. 在須要權限控制的地方,使用函數來判斷用戶是否有權限訪問

我這裏採用的是結局方案2.由於比較方便, 可是問題來了 express+pug是不支持異步的寫法,而acl提供給咱們的全是異步的, 由於時間緣由,我沒有去深究裏面的判斷,而是採用了一種耦合性比較高可是比較方便的判斷方法.session

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在加載的時候就已經被寫入內存了,因此性能消耗不會特別大。好比下面的例子。app

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函數就很麻煩了。 因此在這種狀況下 有如下幾個解決方案

  1. 從acl源碼入手
  2. 每次渲染的時候就把數據準備好
const hasBtn1Role = hasRole(user.role, '/xxx','get');
res.render('a.pug',{hasBtn1Role})
複製代碼
相關文章
相關標籤/搜索