node_acl 权限管理路径通配

小编 2026-06-05 阅读:1424 评论:0
最近做一个基于nodejs的权限管理,查阅了一两天,发现大致是这样的:passportjsnod...

最近做一个基于nodejs的权限管理,查阅了一两天,发现大致是这样的:
passportjs
node-oauth
rbac
node_acl
express_acl
connect-roles

需求

  1. 按照模块,页面,API等级别做权限控制,暂时不需要做到按钮级别
  2. 主要程序开发完毕,需要侵入少
  3. 存储主要考虑redis
  4. 自己开发管理页面,方便自定义和维护

选取原则

  • 轻量级别
    passportjs太强大,大到怕怕
  • 文档清晰(示例|API)
    node_acl的readme 我只能说,真的是很友好,API也不多但是都非常get到点
  • 好上手
  • 容易扩展
  • 功能强大或者适用
  • 代码侵入少
    最讨厌到处修改代码
  • 人气指数相对比较高

最后选择了node_acl,主要是

  1. 人气相对比较高,大约2000star,
  2. 其次功能本身很独立,提供内存,mongo和redis三种存储方式
  3. API简单好用
  4. 文档好读
  5. 源码不多,方便去自定义和扩展
  6. 我们主要程序开发完毕,需要侵入少,研究后发现node_acl应该可以

确认node_acl后,就开始研究一些小细节和设计,说这么多,就是自己写一点代码进行接口功能测试。

问题列表:

  1. 权限继承
    addRoleParents满足需求,确实好用。比如guest, user ,admin三个Role, user集成guest, admin集成user。然后对不同的Role进行权限配置。还是不错的
  2. Resource 不支持同配
    这是什么意思,比如消息有下面几个路径, /msg/delete, msg/add, /msg/list,你就必须一条一条的配置,是不是很狗血
    Added the possibility to have a wildcard in resource name
  3. 不提供所有的Role查询的API
    我进入后,居然不知道有多少种Role
  4. 删除角色后,数据有残存
  5. 需要引入模块来关联页面或者API
  6. 初始化需要有超级管理员
  7. 默认设置,全部允许?全部不允许访问?
  8. 是否引入目录继承关系
  9. 。。。。。。

这里扒拉扒拉写这么多,很多只要API能做到,剩下的就是设计问题。麻烦的问题来了

  1. Resource不支持通配
  2. 不提供所有的Role查询的API

这两个底层基本的功能不支持,还玩个蛋。
冷静,冷静,我们打开源码,会发现,插件一共就7个js(版本0.4.11)

  1. acl.js
    核心之核心文件,暴露Role,Resource, Permission等等的API
  2. backend.js
    backend API定义,并没实际作用
  3. contract.js
    参数验证js
  4. memory-backend.js
    内存中存储
  5. mongodb-backend.js
    mongodb存储
  6. redis-backend.js
    redis存储
  7. index.js
    默认文件

三种backend都是存储数据的,那我们先导出数据来看一看:
关于怎么导出redis

  1. 安装redis-dump
  2. edis-dump -h 127.0.0.1 -d 0 --json > c:db.json

我们看一看导出的文件

{    "acl_allows_/@guest": {        "type": "set",        "value": [            "*"        ]    },    "acl_allows_/about@guest": {        "type": "set",        "value": [            "*"        ]    },    "acl_allows_/index@guest": {        "type": "set",        "value": [            "*"        ]    },    "acl_meta@roles": {        "type": "set",        "value": [            "guest"        ]    },    "acl_meta@users": {        "type": "set",        "value": [            "1024"        ]    },    "acl_resources@guest": {        "type": "set",        "value": [            "/",            "/about",            "/index"        ]    },    "acl_roles@user": {        "type": "set",        "value": [            "1024"        ]    },    "acl_users@1024": {        "type": "set",        "value": [            "user"        ]    }}
  1. acl是前缀,在初始化acl的时候可以设置

    var acl = require('acl');// Using redis backendacl = new acl(new acl.redisBackend(redisClient, 'acl'));
  2. acl_meta@roles,acl_meta@users,acl_meta@users
    acl_meta@roles就表示存储的所有的Role, 其他的同理
    翻到代码acl.js 看看系统是怎么取某个用户的Roles的

 /**  userRoles( userId, function(err, roles) )  Return all the roles from a given user.  @param {String|Number} User id.  @param {Function} Callback called when finished.  @return {Promise} Promise resolved with an array of user roles*/Acl.prototype.userRoles = function(userId, cb){  return this.backend.getAsync(this.options.buckets.users, userId).nodeify(cb);};

this.options.buckets.users是个什么鬼,翻到顶部,

   options = _.extend({    buckets: {      meta: 'meta',      parents: 'parents',      permissions: 'permissions',      resources: 'resources',      roles: 'roles',      users: 'users'    }  }, options);

this.options.buckets.users: 就是users文本,那么联想这几个参数
'acl','users' ,'1024', 再看看,你是不是很惊喜,很意外。

    "acl_users@1024": {        "type": "set",        "value": [            "user"        ]    }

其实很简单,redis-backend.js里面有个方法叫做 bucketKey,专门用户拼接存储的key,
所以,你想获得什么数据,思路就很简单了,

  bucketKey : function(bucket, keys){    var self = this;    if(Array.isArray(keys)){      return keys.map(function(key){        return self.prefix+'_'+bucket+'@'+key;      });    }else{      return self.prefix+'_'+bucket+'@'+keys;    }  }

现在我们要获取当前所有的角色,怎么获取了,这个主要给超级管理员。
我们只要拼接处 acl_meta@roles,就可以获得所有的角色了。

/**  allRoles( userId)  获得所有的Role  @param {String|Number}用户Id **/Acl.prototype.allRoles = function (userId) {    contract(arguments)        .params('string|number')        .end()    return userId ? this.userRoles(userId) :        this.backend.getAsync(this.options.buckets.meta, this.options.buckets.roles)            .then(roles => roles.filter(r => !!r))}

到上面为止,我们分析数据结构之后,我们可以获取很多接口并没有暴露的数据了。
回到我们最关心的问题,这个不支持通匹配,怎么办???

  1. Mongodb-backend
    项目主要考虑redis,这个不得己不会考虑
  2. 已有插件
    查询了一遍,node_acl的插件倒是有几个,好像都是支持更多存储的
  3. 自定义扩展
  4. 目录权限继承
    这个倒是可以考虑
  5. 手动维护,配合 acl.middleware的第一个参数,限定目录
    这个很尴尬
  6. Acl.middleware + 额外开发中间件()
    修改比较多,感觉不好
  7. await next()之后,再返回前重新拦截
    很无赖的想法

这里就先有限考虑自定义扩展,先静静的看看API,思路如下:

  1. userId => roles (userRoles)
  2. roles => resources | 依据实际条件缓存 (whatResources)
  3. 通过resources来匹配path,查找到满足条件的resources|resource
  4. 通过匹配的resource查询访问权限 (isAllowed)

1,2,4都是有现成的API,唯独3要自己实现,这里就要提到 path-to-regexp, express和koa都是基于这个来显示路由匹配的,那么我就有了上面的想法。

/**  getMappedRerouces(path,resources)  获得用户有关联的所有资源  @param {String|Number}当前要匹配的路径  @param {Array}当前用户可以访问的所有Resource*/function getMappedRerouces(path, resources) {    return [].concat(resources.filter(r = dbRe => {        //TODO:: 第二个参数option调研        let re = pathToRegexp(dbRe)        return !!re.exec(path)    }))}

这个就可以获取当前请求path匹配的所有Resource,
很可能是多条,那么怎么办,任何一条匹配就应该是可以。
那么我们上最后的代码

const Acl = require('acl')const contract = require('../node_modules/_acl@0.4.11@acl/lib/contract')const pathToRegexp = require('path-to-regexp')const originalIsAllowed = Acl.prototype.isAllowed/**  getMappedRerouces(path,resources)  获得用户有关联的所有资源  @param {String|Number}当前要匹配的路径  @param {Array}当前用户可以访问的所有Resource*/function getMappedRerouces(path, resources) {    return [].concat(resources.filter(r = dbRe => {        //TODO:: 第二个参数option调研        let re = pathToRegexp(dbRe)        return !!re.exec(path)    }))}/**  getAllResources( userId)  获得用户有关联的所有资源  @param {String|Number}用户Id */Acl.prototype.allResources = function (userId) {    contract(arguments)        .params('string|number')        .end()    return userId ? this.userRoles(userId).then(roles => this.whatResources(roles)) : this._allResources()}Acl.prototype._allResources = function () {    return this.allRoles()        .then(roles => this.backend.unionAsync(this.options.buckets.resources, roles))}/**  allRoles( userId)  获得所有的Role  @param {String|Number}用户Id */Acl.prototype.allRoles = function (userId) {    contract(arguments)        .params('string|number')        .end()    return userId ? this.userRoles(userId) :        this.backend.getAsync(this.options.buckets.meta, this.options.buckets.roles)            .then(roles => roles.filter(r => !!r))}/**  isAllowed( userId, resource, permissions, function(err, allowed) )  Checks if the given user is allowed to access the resource for the given  permissions (note: it must fulfill all the permissions).  @param {String|Number} User id.  @param {String|Array} resource(s) to ask permissions for.  @param {String|Array} asked permissions.  @param {Function} Callback called wish the result.*/Acl.prototype.isAllowed = function (userId, resource, permissions, cb) {    contract(arguments)        .params('string|number', 'string', 'string|array', 'function')        .params('string|number', 'string', 'string|array')        .end();    let args = [...arguments]    // 1.userId => roles    // 2.roles => resources | 依据实际条件缓存    // 3.通过resources来匹配path,查找到满足条件的resources|resource    // 4.通过匹配的resource查询访问权限    return this.allResources(userId)        .then(dbRe => getMappedRerouces(resource, Object.keys(dbRe)))        .then(resources => {            // 多个resource匹配的情况                     return Promise.all((resources || []).map(re => {                return originalIsAllowed.apply(this, [args[0], re, ...args.slice(2)])            }))        }).then(allows => {            return allows.some(Boolean)        })}module.exports = Acl

怎么使用,

  1. 权限设置
  2. 中间件拦截

权限设置

    acl.allow([                {            roles: 'user',            allows: [                {                    resources: ['/msg', '/msg/:id', '/download', '/activities','/msg/(.*)'],                    permissions: '*'                }            ]        }  ])

中间件拦截

const acl = require('../acl')//const getAllRouter = require('./util/getAllRouter')const pathToRegexp = require('path-to-regexp')const loginPath = '/login'module.exports = app => {    async function aclmd(req, res, next) {        var userId = 1024        if (userId) {            const path = req.path            if (path == loginPath) {                await next()            } else {                //const aa = await anyMatch(path, userId, acl)                const allowed = await acl.isAllowed(userId, path, '*')                if (allowed) {                                      next()                } else {                                  res.redirect(loginPath)                    res.end();                }            }        } else {            res.redirect(loginPath)            res.end();        }    }    app.use(aclmd)}

node acl demo
node权限控制模块node_acl的应用

版权声明

本文仅代表作者观点,不代表百度立场。
本文系作者授权百度百家发表,未经许可,不得转载。

热门文章
  • 机房智能化温湿度解决方式之POE供电以太网温湿度传感器

    机房智能化温湿度解决方式之POE供电以太网温湿度传感器
    机房智能化温湿度解决方式之POE供电以太网温湿度传感器 北京盈创力和电子科技有限公司 智能型TCP网口温湿度记录仪 北京IP网络温湿度记录仪厂家,北京盈创力和 北京智能型TCP网口温湿度记录仪IP网络温湿度记录仪是一种新型的基于TCP/IP协议双绞线以太网标准温湿度采集模块,利用它可以实现现场温度值、相对湿度值的采集,同时利用其自身的RJ45通信接口可以方便地和机房监控主机或交换机集线器进行联网。 工作于-40℃~85℃工业级带...
  • Sequential Monte Carlo Methods (SMC) 序列蒙特卡洛/粒子滤波/Bootstrap Filtering

    Sequential Monte Carlo Methods (SMC) 序列蒙特卡洛/粒子滤波/Bootstrap Filtering
    Problem Statement 我们考虑一个具有马尔可夫性质、非线性、非高斯的状态空间模型(State Space Model):对于一个时间序列上的观测结果{yt,t∈N}\\{ y_t , t \\in N \\}{yt​,t∈N},我们认为每个观测结果yty_tyt​的生成依赖于一个无法直接观察的隐变量xt∈{xt,t∈N}x_t \\in \\{x_t , t \\in N \\}xt​∈{xt​,t∈N},即:p(...
  • HTTP状态保持的原理

    HTTP状态保持的原理
    a)在用户登录之后,浏览器返回响应的时候会在响应中添加上cookieb)浏览器接收到cookie之后会自动保存c)当用户再次请求同一服务器中的其他网页的时候,浏览器会自动带上之前保存的cookied)服务接收到请求之后可以请 request 对象中取到cookie 判断当前用户是否登录  Http是无状态的,就是连接时数据互通,关闭后...
  • Hive 系统函数及示例

    Hive 系统函数及示例
    查看所有系统函数 show functions; 函数分类 内置函数【系统函数】 数学函数: floor、round、ceil、cos、log2等 字符串函数: length、reverse、trim、lower、get_json_object、repeat等 收集函数: size 转换函数: cast 日期函数: year、month、datediff、date、date_add等 条件函数: coalesce、case…w...
  • CSRF的原理和防范措施

    CSRF的原理和防范措施
    a)攻击原理:i.用户C访问正常网站A时进行登录,浏览器保存A的cookieii.用户C再访问攻击网站B,网站B上有某个隐藏的链接或者图片标签会自动请求网站A的URL地址,例如表单提交,传指定的参数iii.而攻击网站B在访问网站A的时候,浏览器会自动带上网站A的cookieiv.所以网站A在接收到请求之后可判断当前用户是登录状态,所以...
标签列表