说明
Q: 这个工具用来做什么的呢
A: 用户有不同的权限,比如管理员,vip,普通用户,每个用户对应访问api,页面都不一样
nodejs有两个比较有名的权限管理模块 一个是acl 一个是rbac 综合对比了一下最终在做项目的时候选择了acl
功能列表:
- addUserRoles //给某用户添加角色
- removeUserRoles //移除某用户角色
- userRoles //获取某用户所有角色
- roleUsers //获取所有是此角色的用户
- hasRole // 某用户是否是某角色
- addRoleParents //给某角色增加父角色
- removeRoleParents //移除某觉得的某父角色或所有父角色
- removeRole //移除某角色
- removeResource //移除某资源
- allow //给某些角色增加某些资源的某些权限
- removeAllow //移除某些角色的某些资源的某些权限
- allowedPermissions //查询某人的所有资源及其权限
- isAllowed //查询某人是否有某资源的某权限
- areAnyRolesAllowed //查询某角色是否有某资源的某权限
- whatResources //查询某角色有哪些资源
- middleware //middleware for express
- backend //指定方式(mongo/redis…)
ACL名词及其主要方法
roles 角色
- removeRole
- addRoleParents
- allow
- removeAllow
resources 资源
- whatResources
- removeResource
permissions 权限
users 用户
- allowedPermissions
- isAllowed
- addUserRoles
- removeUserRoles
- userRoles
- roleUsers
- hasRole
- areAnyRolesAllowed
使用方法
const acl = new Acl(new Acl.memoryBackend()); // eslint-disable-line
acl.allow(aclConfig);
return acl;
};
// acl_conf
module.exports = [
{
roles: 'normal',// 一般用户
allows: [
{ resources: ['/admin/reserve'],permissions: ['get'] },]
},{
roles: 'member',// 会员
allows: [
{ resources: ['/admin/reserve','/admin/sign'],{ resources: ['/admin/reserve/add-visitor','/admin/reserve/add-visitor-excel','/admin/reserve/audit','/admin/sign/ban'],permissions: ['post'] },{
roles: 'admin',// 管理
allows: [
{ resources: ['/admin/reserve','/admin/sign','/admin/set'],{ resources: ['/admin/set/add-user','/admin/set/modify-user'],{
roles: 'root',// 最高权限
allows: [
{ resources: ['/admin/reserve',]
}
];
校检
这里是结合express做校检...结果发现acl自己提供的中间件太鸡肋了,这里就重写了一个。
// 容错 如果访问的是 /admin/sign/ 后面为 /符号认定也为过
if (resource[resource.length - 1] === '/') {
resource = resource.slice(0,-1);
}
let role = await acl.hasRole(req.session.userName,'root');
if (role) {
return next();
}
let result = await acl.isAllowed(req.session.userName,resource,req.method.toLowerCase());
// if (!result) {
// let err = {
// errorCode: 401,// message: '<a href="/tag/yonghu/" target="_blank" class="keywords">用户</a>未授权访问',// };
// return res.status(401).send(err.message);
// }
next();
};
}
有点要说明的是express.Router支持导出一个Router模块 再在app.use使用,但是如果你这样使用 app.use('/admin/user',auth(),userRoute); 那么是在auth这个函数是获取不到 req.route 这个属性的。 因为acl对访问权限做的是强匹配,所以需要有一定的容错
登录的权限分配
result为数据库查询出来的用户信息,或者后台api返给的用户信息,这里的switch可以使用配置文件的形式,因为我这边本次项目只有三个权限,所以就在这里简单写了一下。
case 0:
roleName = 'admin';
break;
case 1:
roleName = 'normal';
break;
case 2:
roleName = 'member';
break;
}
if (result.result.name === 'Nathan') {
roleName = 'root';
}
req.session['role'] = roleName;
// req.session['role'] = 'root'; // test
acl.addUserRoles(result.result.name,roleName);
// acl.addUserRoles(result.result.name,'root'); // test
pug页面中的渲染逻辑控制
在 express+pug中 app.locals.auth= async function(){} 这个写法在pug渲染的时候是不会得出最终结果的,因为pug是同步的,那么我如何控制@R_502_340@或者说@R_502_340@的按钮用户是否有权限展示出来,这里通用的做法有
我这里采用的是结局方案2.因为比较方便,但是问题来了 express+pug是不支持异步的写法,而acl提供给我们的全是异步的,因为时间原因,我没有去深究里面的判断,而是采用了一种耦合性比较高但是比较方便的判断方法.
return true;
}
const current = aclConf.find((n) => {
return n['roles'] === userRole;
});
let isFind = false;
for (let i of current.allows) {
const currentPath = i.resources; // 目前数组第一个为单纯的get路由
isFind = currentPath.includes(path);
if (isFind) {
// 如果找到包含该路径 并且method也对应得上 那么则通过
if (i.permissions.includes(method)) {
break;
}
// 如果找到该路径 但是method对应不上 则继续找.
continue;
}
}
return isFind;
};
上述代码页比较简单,去遍历acl_conf,查找用户是否有@R_502_340@的或者按钮的权限 因为acl_conf在加载的时候就已经被写入内存了,所以性能消耗不会特别大。比如下面的例子。
结尾
依靠acl这个组件可以快速打造一个用户的权限管理模块。 但是还有个问题 也急速那个app.locals.hasRole函数,如果你使用removeAllow动态改变了用户的权限表,那么hasRole函数就很麻烦了。 所以在这种情况下 有以下几个解决方案
- 从acl源码入手
- 每次渲染的时候就把数据准备好