最近做一个基于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. NV12和NV21转rgb

    void NV21_T_RGB(unsigned int width , unsigned int height , unsigned char *yuyv , unsigned char *rgb) ...

  2. 操作系统处理内存时内存页为4k

    windows和unix处理内存时,一个内存页的大小都为4k. 测试代码 int main() { while (1) { int *p = (int *)malloc(1024); getchar( ...

  3. UNIX网络编程——基于UDP协议的网络程序

    一.下图是典型的UDP客户端/服务器通讯过程 下面依照通信流程,我们来实现一个UDP回射客户/服务器: #include <sys/types.h> #include <sys/so ...

  4. (九十)使用多个storyboard+代码实现控制器的分开管理

    使用单个storyboard会使得项目难与管理,使用纯代码又会过于麻烦,因此如果能将二者结合起来,并且使用多个storyboard,会使得项目简单简单.方便许多. 下面以一个简单的视图关系为例,介绍多 ...

  5. (国内)完美下载Android源码Ubuntu版

    今天写的文章莫名奇妙的没了,所以再重新写一篇. 首先,为了方便起见,我已经将系统更换成里Ubuntu,因为官方推荐使用这个Linux发行版.先来一张系统的截图: Ubuntu的版本是16.04(推荐用 ...

  6. 转换xml格式的短信记录

    <?xml version="1.0" encoding="UTF-8"?> <SMSRecord>     <SMS>   ...

  7. Chapter 2 User Authentication, Authorization, and Security(9):防止登录名和用户查看元数据

    原文出处:http://blog.csdn.net/dba_huangzj/article/details/39003679,专题目录:http://blog.csdn.net/dba_huangzj ...

  8. 高通 8x12 添加 TP和按键

    1 .在tp的驱动文件中添加以下代码实现按键功能 [plain] view plain copy static ssize_t ft5x06_virtual_keys_register(struct  ...

  9. [WinForm]dataGridView动态加载以本地图片显示列

    增加一个图片列: C# private void btnQuery_Click(object sender, EventArgs e) { StringBuilder sb=new StringBui ...

  10. C++ Primer 有感(命名的强制类型转换)

    C++四种强制类型转换的方法以及其应用场合,之前有看过这个知识点,但是,面试的时候怎么想也就没有写的很全面,于是,这里整理一下: C++中的四种强制类型转换除了具有C语言强制类型转换的功能外,还可提供 ...