最近做一个基于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 backend
acl = new acl(new acl.redisBackend(redisClient, 'acl'));
  1. 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的应用

node_acl 路径通配的更多相关文章

  1. linux学习14 Linux运维高级系统应用-glob通配及IO重定向

    一.回顾 1.bash基础特性:命令补全,路径补全,命令引用 2.文件或目录的复制,移动及删除操作 3.变量:变量类型 存储格式,数据表示范围,参与运算 二.bash的基础特性 1.globbing: ...

  2. floodlight StaticFlowPusher 基于网段写flow,通配

    flow1 = { "switch":"00:00:00:00:00:00:00:03", "name":"flow-mod-1& ...

  3. rsyslog 一重启就会开始同步之前所有通配的日志文件

    <pre name="code" class="html">[root@dr-mysql01 zjzc_log]# grep '24/Sep/201 ...

  4. Perl文件名通配和文件查找

    在shell中使用*来对文件名进行通配扩展,在Perl中也同样支持文件名通配.而且perl中的glob通配方式和shell的通配方式完全一致,实际上perl的glob函数就是直接调用csh来通配的(如 ...

  5. IIS 使用多个https和通配证书解决方案

    环境:OS :WINDOWS 2008 IIS: IIS7 域名:三个二级域名 问题:由于一个网站只支持一个443,但可以通过更改配置得到绑定不同域名.但由于公用证书,所以问题出来.只能为一个二级域名 ...

  6. Let's Encrypt 免费通配 https 签名证书 安装方法2 ,安卓签名无法认证!

    Let's Encrypt 免费通配 https 签名证书 安装方法 按照上文 配置完毕后你会发现 在pc浏览器中正常访问,在手机浏览器中无法认证 你只需要安装一个或多个中级证书 1.查看Nginx ...

  7. 赛门铁克通配符SSL证书,一张通配型证书实现全站加密

      赛门铁克通配型SSL证书,验证域名所有权和企业信息,属于企业验证(OV) 级SSL证书,最高支持256位加密.申请通配符SSL证书可以保护相同主域名下无限数量的多个子域名(主机).例如,一个通配符 ...

  8. linux globbing文件名通配

    globbing:文件名通配 元字符: *:匹配任意长度的任意字符 ?:匹配任意单个字符 []:匹配指定范围内的任意单个字符 [a-z]或者[A-Z]或者[[:alpha:]]:匹配任意一个字母 [[ ...

  9. CSS ID选择器&通配选择器

    ID选择器 ID(IDentity)是编号的意思,一般指定标签在HTML文档中的唯一编号.ID选择器和标签选择器.类选择器的作用范围不同. ID选择器仅仅定义一个对下对象的样式,而标签选择器和类选择器 ...

随机推荐

  1. 3.QT中的debug相关的函数,以及文件锁的使用

     1  新建项目T33Debug main.cpp #include <QDebug> #include <QFile> #include <QMutex>   ...

  2. scala学习笔记1(表达式)

    <pre name="code" class="plain">//Scala中的 main 函数需要存在于 object 对象中,我们需要一个obj ...

  3. 【一天一道LeetCode】#118. Pascal's Triangle

    一天一道LeetCode 本系列文章已全部上传至我的github,地址:ZeeCoder's Github 欢迎大家关注我的新浪微博,我的新浪微博 欢迎转载,转载请注明出处 (一)题目 Given n ...

  4. 利用openssl管理证书及SSL编程第3部分:将MinGW编译的openssl dll导出def和lib供MSVC使用

    将MinGW编译的openssl dll导出def和lib供MSVC使用 前面我们用mingw把openssl 编译成了动态库,得到下面2个dll文件: libeay32.dll ssleay32.d ...

  5. 1017. Queueing at Bank (25) - priority_queuet

    题目如下: Suppose a bank has K windows open for service. There is a yellow line in front of the windows ...

  6. 【Unity技巧】开发技巧(技巧篇)

    写在前面 和备忘录篇一样,这篇文章旨在总结Unity开发中的一些设计技巧,当然这里只是我通过所见所闻总结的东西,如果有不对之处欢迎指出. 技巧1:把全局常量放到一个单独的脚本中 很多时候我们需要一些常 ...

  7. android异步任务asyntask详解

    在Android中实现异步任务机制有两种方式,Handler和AsyncTask. Handler模式需要为每一个任务创建一个新的线程,任务完成后通过Handler实例向UI线程发送消息,完成界面的更 ...

  8. TCP的ACK确认系列 — 发送状态转换机

    主要内容:TCP的ACK发送方式,以及ACK发送状态转换机的实现. 内核版本:3.15.2 我的博客:http://blog.csdn.net/zhangskd 概述 TCP采用两种方式来发送ACK: ...

  9. GDAL中MEM格式的简单使用示例

    GDAL库中提供了一种内存文件格式--MEM.如何使用MEM文件格式,主要有两种,一种是通过别的文件使用CreateCopy方法来创建一个MEM:另外一种是图像数据都已经存储在内存中了,然后使用内存数 ...

  10. 控件的基本使用-iOS—UI笔记

    学习目标 1.[掌握]第一个UI项目 2.[掌握]控件连线 3.[掌握]按钮的基本操作 4.[掌握]控件的常用属性 一.第一个UI项目 UI (User Interface)也是就用户界面,是App的 ...