node_acl 路径通配
最近做一个基于nodejs的权限管理,查阅了一两天,发现大致是这样的:
passportjs
node-oauth
rbac
node_acl
express_acl
connect-roles
需求
- 按照模块,页面,API等级别做权限控制,暂时不需要做到按钮级别
- 主要程序开发完毕,需要侵入少
- 存储主要考虑redis
- 自己开发管理页面,方便自定义和维护
选取原则
- 轻量级别
passportjs太强大,大到怕怕 - 文档清晰(示例|API)
node_acl的readme 我只能说,真的是很友好,API也不多但是都非常get到点 - 好上手
- 容易扩展
- 功能强大或者适用
- 代码侵入少
最讨厌到处修改代码 - 人气指数相对比较高
最后选择了node_acl,主要是
- 人气相对比较高,大约2000star,
- 其次功能本身很独立,提供内存,mongo和redis三种存储方式
- API简单好用
- 文档好读
- 源码不多,方便去自定义和扩展
- 我们主要程序开发完毕,需要侵入少,研究后发现node_acl应该可以
确认node_acl后,就开始研究一些小细节和设计,说这么多,就是自己写一点代码进行接口功能测试。
问题列表:
- 权限继承
addRoleParents满足需求,确实好用。比如guest, user ,admin三个Role, user集成guest, admin集成user。然后对不同的Role进行权限配置。还是不错的 - Resource 不支持同配
这是什么意思,比如消息有下面几个路径, /msg/delete, msg/add, /msg/list,你就必须一条一条的配置,是不是很狗血
Added the possibility to have a wildcard in resource name - 不提供所有的Role查询的API
我进入后,居然不知道有多少种Role - 删除角色后,数据有残存
- 需要引入模块来关联页面或者API
- 初始化需要有超级管理员
- 默认设置,全部允许?全部不允许访问?
- 是否引入目录继承关系
- 。。。。。。
这里扒拉扒拉写这么多,很多只要API能做到,剩下的就是设计问题。麻烦的问题来了
- Resource不支持通配
- 不提供所有的Role查询的API
这两个底层基本的功能不支持,还玩个蛋。
冷静,冷静,我们打开源码,会发现,插件一共就7个js(版本0.4.11)
- acl.js
核心之核心文件,暴露Role,Resource, Permission等等的API - backend.js
backend API定义,并没实际作用 - contract.js
参数验证js - memory-backend.js
内存中存储 - mongodb-backend.js
mongodb存储 - redis-backend.js
redis存储 - index.js
默认文件
三种backend都是存储数据的,那我们先导出数据来看一看:
关于怎么导出redis
- 安装redis-dump
- 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"
]
}
}
- acl是前缀,在初始化acl的时候可以设置
var acl = require('acl');
// Using redis backend
acl = new acl(new acl.redisBackend(redisClient, 'acl'));
- 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))
}
到上面为止,我们分析数据结构之后,我们可以获取很多接口并没有暴露的数据了。
回到我们最关心的问题,这个不支持通匹配,怎么办???
- Mongodb-backend
项目主要考虑redis,这个不得己不会考虑 - 已有插件
查询了一遍,node_acl的插件倒是有几个,好像都是支持更多存储的 - 自定义扩展
- 目录权限继承
这个倒是可以考虑 - 手动维护,配合 acl.middleware的第一个参数,限定目录
这个很尴尬 - Acl.middleware + 额外开发中间件()
修改比较多,感觉不好 - await next()之后,再返回前重新拦截
很无赖的想法
这里就先有限考虑自定义扩展,先静静的看看API,思路如下:
- userId => roles (userRoles)
- roles => resources | 依据实际条件缓存 (whatResources)
- 通过resources来匹配path,查找到满足条件的resources|resource
- 通过匹配的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
怎么使用,
- 权限设置
- 中间件拦截
权限设置
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 路径通配的更多相关文章
- linux学习14 Linux运维高级系统应用-glob通配及IO重定向
一.回顾 1.bash基础特性:命令补全,路径补全,命令引用 2.文件或目录的复制,移动及删除操作 3.变量:变量类型 存储格式,数据表示范围,参与运算 二.bash的基础特性 1.globbing: ...
- floodlight StaticFlowPusher 基于网段写flow,通配
flow1 = { "switch":"00:00:00:00:00:00:00:03", "name":"flow-mod-1& ...
- rsyslog 一重启就会开始同步之前所有通配的日志文件
<pre name="code" class="html">[root@dr-mysql01 zjzc_log]# grep '24/Sep/201 ...
- Perl文件名通配和文件查找
在shell中使用*来对文件名进行通配扩展,在Perl中也同样支持文件名通配.而且perl中的glob通配方式和shell的通配方式完全一致,实际上perl的glob函数就是直接调用csh来通配的(如 ...
- IIS 使用多个https和通配证书解决方案
环境:OS :WINDOWS 2008 IIS: IIS7 域名:三个二级域名 问题:由于一个网站只支持一个443,但可以通过更改配置得到绑定不同域名.但由于公用证书,所以问题出来.只能为一个二级域名 ...
- Let's Encrypt 免费通配 https 签名证书 安装方法2 ,安卓签名无法认证!
Let's Encrypt 免费通配 https 签名证书 安装方法 按照上文 配置完毕后你会发现 在pc浏览器中正常访问,在手机浏览器中无法认证 你只需要安装一个或多个中级证书 1.查看Nginx ...
- 赛门铁克通配符SSL证书,一张通配型证书实现全站加密
赛门铁克通配型SSL证书,验证域名所有权和企业信息,属于企业验证(OV) 级SSL证书,最高支持256位加密.申请通配符SSL证书可以保护相同主域名下无限数量的多个子域名(主机).例如,一个通配符 ...
- linux globbing文件名通配
globbing:文件名通配 元字符: *:匹配任意长度的任意字符 ?:匹配任意单个字符 []:匹配指定范围内的任意单个字符 [a-z]或者[A-Z]或者[[:alpha:]]:匹配任意一个字母 [[ ...
- CSS ID选择器&通配选择器
ID选择器 ID(IDentity)是编号的意思,一般指定标签在HTML文档中的唯一编号.ID选择器和标签选择器.类选择器的作用范围不同. ID选择器仅仅定义一个对下对象的样式,而标签选择器和类选择器 ...
随机推荐
- love~LBJ,奥布莱恩神杯3
时间:2016年6月20日8:00:地点:美国金州甲骨文(Oracle)球馆:事件:G7大战,又一次见证伟大赛季奥布莱恩神杯得主--金州勇士VS克利夫兰骑士...... 可以说,这是NBA以来 ...
- J2EE进阶(十二)SSH框架整合常见问题汇总(三)
在挂失用户时,发现userid值为空,但是在前台输入处理账号22时,通过后台输出可以看出,后台根据前端输入在数据库中查询到结果对象并输出该对象的userid,而且Guashi对象也获取到了其值. 解决 ...
- Android读取网络图片到本地的简约的实现
今天在网上看到了一个关于读取网络文件的小视频,觉得不错,拿来与大家分享 思路 具体的思路比较的简单,但是思想非常的单纯.那就是输入一个网址,点击按钮,将从网络上获取的一张图片显示到一个ImageVie ...
- Uva - 1589 - Xiangqi
Xiangqi is one of the most popular two-player board games in China. The game represents a battle bet ...
- 【Unity Shaders】Transparency —— 透明的cutoff shader
本系列主要参考<Unity Shaders and Effects Cookbook>一书(感谢原书作者),同时会加上一点个人理解或拓展. 这里是本书所有的插图.这里是本书所需的代码和资源 ...
- 《java入门第一季》之泛型方法和泛型接口
一.泛型方法. /* * 泛型方法:把泛型定义在方法上.格式:public <泛型类型> 返回类型 方法名(泛型类型 t) public <T> void show(T t){ ...
- ROS_Kinetic_10 ROS程序基础Eclipse_C++(一)
ROS_Kinetic_10 ROS程序基础Eclipse_C++(一) 编写简单的消息发布器和订阅器 (C++) http://wiki.ros.org/cn/ROS/Tutorials/Writi ...
- 过时api LocalActivityManager 作用
换了个新工作,看公司代码还在用LocalActivityManager类 不知道是个什么东西,百度了也没具体介绍查了下sdk是这样介绍的 LocalActivityManager是一个助手类,在同一个 ...
- ceres-solver库使用示例
上一篇博客大致说明了下ceres-solver库的编译,然后形成了一个二次开发的库,下面就是用这个二次开发库来写一个简单(其实不太简单)的DEMO来演示ceres-solver库的强大.我们以求解一个 ...
- Cocos2D遍历场景图(Scene Graph)
另一个Cocos2D有用的调试特性是打印出递归的打印出节点的孩子们. 你可以添加以下一行到MainScene或GameScene的didLoadFromCCB的方法中: [self.scene wal ...