最近做一个基于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. Java基础---Java---基础加强---内省的简单运用、注解的定义与反射调用、 自定义注解及其应用、泛型及泛型的高级应用、泛型集合的综合

    内省的简单运用: JavaBean是一种特殊的Java类,主要用于传递数据信息,这种java类中的方法主要用于访问私有的字段,且方法名符合某种命名规则. 采用遍历BeanInfo的所有属性方式来查找和 ...

  2. Cocos2d-x 添加iOS7默认分享/AirDrop

    猴子原创,欢迎转载.转载请注明: 转载自Cocos2D开发网–Cocos2Dev.com,谢谢! 原文地址: http://www.cocos2dev.com/?p=530 下午添加分享的时候,看着这 ...

  3. 05 Activity 跳转传值

    第一个Activity: package com.fmyboke; import java.io.Serializable; import java.util.ArrayList; import ja ...

  4. Android开发学习之路--百度地图之初体验

    手机都有gps和网络,通过gps或者网络可以定位到自己,然后通过百度,腾讯啊之类的地图可以显示我们的地理位置.这里学习下百度地图的使用.首先就是要申请开发者了,这个详细就不多讲了.http://dev ...

  5. Xcode7 真机免证书调试Cocos2D游戏

    大熊猫猪·侯佩原创或翻译作品.欢迎转载,转载请注明出处. 如果觉得写的不好请多提意见,如果觉得不错请多多支持点赞.谢谢! hopy ;) 经过一番实验,现在终于可以在Xcode7上免证书真机调试了: ...

  6. Android的PopupWindow使用android学习之旅(四十三)

    PopupWindow简介 PopupWindow是一个类似dialog的控件,可以接受任何的view作为下拉列表显示. 用法 代码展示 package peng.liu.test; import a ...

  7. UNIX网络编程——shutdown 与 close 函数 的区别

    假设server和client 已经建立了连接,server调用了close, 发送FIN 段给client(其实不一定会发送FIN段,后面再说),此时server不能再通过socket发送和接收数据 ...

  8. Java-IO之FileDescriptor

    FileDescriptor是文件描述符,可以被用来表示开放文件,开放套接字等,FileDescriptor可以被看成某个文件,但无法对该文件进行操作,需要新创建FileDescriptor对应的Fi ...

  9. UNIX环境高级编程——Linux系统调用列表

    以下是Linux系统调用的一个列表,包含了大部分常用系统调用和由系统调用派生出的的函数.这可能是你在互联网上所能看到的唯一一篇中文注释的Linux系统调用列表,即使是简单的字母序英文列表,能做到这么完 ...

  10. Caffe框架,训练model并测试数据

    1. 训练model #!/usr/bin/env sh ./build/tools/caffe train --solver=examples/focal_length/focal_solver.p ...