Node.js 实现 RBAC 权限模型

一,简单的权限模型

有个访问权限列表如下:

用户1 --> 查看文件
用户1 --> 编辑文件
用户2 --> 查看文件
用户2 --> 编辑文件  
...

每个用户的每个权限,都保存在该表中,这种直接将权限绑定在用户上的方式就叫做基于用户的权限管理,ACL(Access Control List)。

ACL的优点是简单易用、易于理解。缺点是系统逐渐复杂以后,用户和权限直接挂钩,授予权限时比较分散、不能集中管理,增加了复杂性。

这时,人们就设计了基于角色的访问控制 RBAC(Role Based Access Control)。


二,RBAC 基于角色的访问控制

通过角色关联用户,角色关联权限的方式间接赋予用户权限。相对于 ACL 多了“角色”,只要为某个角色设置好权限,只需调整用户关联的角色权限,即可批量的用户权限调整。

用户和角色是多对一关系,用户和角色是多对多关系。通常,多对多更符合现实世界。比如,一些公司,HR 也兼职行政。

上面这种 RBAC 有时也被称为 RBAC0,在 RBAC0 的基础上,还产生了 RBAC1、RBAC2 和 RBAC3。RBAC1模型,增加了子角色,引入了继承概念,即子角色可以继承父角色的所有权限。RBAC2模型增加了对角色的一些限制:角色互斥、基数约束、先决条件角色等。RBAC3模型包含了RBAC1和RBAC2。

一般情况先使用,使用 RBAC0 模型就可以满足常规的权限管理系统设计。


三,一个简单的 RBAC 实现

/**
 * RBAC
 * 
 * 用户角色关系存储在 tb_user_meta, meta_key='role', meta_value 名字
 */
import dbUtil from './db.util.mjs';


const rbac = {
  roles: ['superadmin', 'admin', 'user', 'guest'],
  permissions: [
    { permission: 'admin.user.show', title: '查看用户' },
    { permission: 'admin.user.store', title: '添加用户' },
    { permission: 'admin.user.update', title: '修改用户' },
    { permission: 'admin.user.destory', title: '删除用户' },
    { permission: 'article.private.view', title: '查看加密文章' }
  ],
  grants: {
    superadmin: [],
    admin: [
      'admin.user.show',
      'admin.user.store',
      'admin.user.update',
      'admin.user.destory',
      'article.private.view'
    ],
    user: [],
    guest: [],
  },
}


/**
 * 获取一个用户所属角色
 * @param {Object} userId 用户ID
 */
async function getRoles(userId){
  let sql = "SELECT * FROM tb_user_meta WHERE user_id=? AND meta_key='role'";
  let [res] = await dbUtil.execute(sql, [userId]);
  let roles = res.map((item, index)=>{
    return item.meta_value
  });
  return roles;
}


/**
 * 获取一个用户所有角色权限的并集
 * @param {Object} userId 用户ID
 */
async function getPermission(userId){
  let roles = await getRoles(userId);
  let permissions = [];
  roles.forEach((item,index)=>{
    permissions = permissions.concat(rbac.grants[item]);
  });
  return permissions;
}


/**
 * 判断一个角色是否有某个权限
 * @param {Object} roleName 角色名
 * @param {Object} permission 标记
 */
async function roleCan(roleName, permission){
  let permissions = rbac.grants[roleName];
  if(permissions == undefined){
    return false;
  }
  return permissions.indexOf(permission) == '-1' ? false : true;
}


/**
 * 判断一个用户是否有某个权限
 * @param {Object} userId 用户ID
 * @param {Object} permission 标记
 */
async function can(userId, permission){
  let rolePermission = await getPermission(userId);
  return rolePermission.indexOf(permission)=='-1' ? false : true;
}


export default {
  getRoles,
  getPermission,
  roleCan,
  can
}


四,使用方法

import rbacUtil from './utils/rbac.util.mjs';

let userId = getUserId();
if( false == rbacUtil.can(userId, 'admin.user.destory') ){
 ctx.status = 403;
 ctx.body = { error:"当前用户没有删除权限" };
 return;
}


修改时间 2023-11-02

声明:本站所有文章和图片,如无特殊说明,均为原创发布。商业转载请联系作者获得授权,非商业转载请注明出处。
随机推荐
Node.js net 模块
Git 放弃本地修改,强制和之前的某次提交同步
JWT 存储在 Cookie 和 Web Storage 的区别
Node.js 控制台进度条实现原理
JavaScript 原生拖放
Cookie 的 HTTP Only 属性
WordPress 输入安全
数据库中间表应该如何命名