Bootstrap

【RBAC鉴权】node-casbin基础教程

一、RBAC概述

RBAC鉴权,完整的英文描述是:Role-Based Access Control,中文意思是:基于角色(Role)的访问控制。这是一种广泛应用于计算机系统和网络安全领域的访问控制模型。

简单来说,就是通过将权限分配给➡角色,再将角色分配给➡用户,来实现对系统资源的访问控制。一个用户拥有若干角色,每一个角色拥有若干权限。这样,就构造成“用户-角色-权限”的授权模型。在这种模型中,用户与角色之间,角色与权限之间,一般者是多对多的关系。具体而言,RBAC模型定义了以下几个核心概心概念:

  • 角色(Role):角色是指在系统中具有一组相关权限的抽象概念,代表了用户在特定上下文中的身份或职能,例如管理员、普通用户等。

  • 权限(Permission):权限是指对系统资源进行操作的许可,如读取、写入、修改等。权限可以被分配给角色。

  • 用户(User):用户是指系统的实际使用者,每个用户可以被分配一个或多个角色。

  • 分配(Assignment):分配是指将角色与用户关联起来,以赋予用户相应的权限。

具体的鉴权逻辑如下图,也可以直接参考RBAC角色权限设计

img_d55912967bd28f3392f56bd216392ec3.png

简单地说,一个用户拥有若干角色,每一个角色拥有若干权限。这样,就构造成“用户-角色-权限”的授权模型。在这种模型中,用户与角色之间,角色与权限之间,一般者是多对多的关系。

二、node-casbin使用模型

这里使用nestjs脚手架做实战演练。

1、安装

# NPM
npm install casbin --save

# Yarn
yarn add casbin

# pnpm
pnpm add casbin

2、开始使用

node-casbin使用模型文件和策略文件新建一个执行器,有关详细信息,请参阅模型部分,也可以直接查看源码中examples目录下的模型文件

  1. auth.controller.ts 控制器入口:

    提供api访问入口:/auth/getPermission

import { Controller, Get, Post, Body, Req } from '@nestjs/common';
import { AuthService } from '@/auth/auth.service';
import { Public } from '@/auth/decorators/public.decorator';
import { ApiTags } from '@nestjs/swagger';

@Controller('auth')
@ApiTags("Auth")
export class AuthController {
  constructor(private readonly authService: AuthService) { }
  @Get('/getPermission')
  getPermission(): any {
    return this.authService.getPermission();
  }
}
  1. auth.services.ts 服务方法:

    提供getPermission()方法。

import { UserService } from '@/module/user/user.service';
import {
  Injectable
} from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { PasswordService } from './password.service';
import Permission from './permission';

@Injectable()
export class AuthService {
  constructor() { }

  async getPermission(): Promise<any> {
    await Permission.init();
    const r:string = Permission.getPermission();
    return r;
  }
}
  1. permission.ts 权限控制类:
import { Enforcer, newEnforcer } from "casbin";

export default class Permission {
  private static enforcerIns: Enforcer

  /**
   * 初始化权限实例
   */
  public static async init() {
    try {
      const path_prefix = "src/auth/permission";   //默认指向项目根目录,不需要:/
      this.enforcerIns = await newEnforcer(`${path_prefix}/basic_model.conf`, `${path_prefix}/basic_policy.csv`);
    } catch (error) {
      throw new Error(error?.message);
    }
  }

  /**
   * 获取权限
   * @param user 用户
   * @param resource 要访问的资源
   * @param action 权限:read、write,此处权限可以进行自定义扩展
   */
  public static async getPermission(user: string = "alice", resource: string = "data1", action: string = "read") {
    // 异步:
    const res:boolean = await this.enforcerIns.enforce(user, resource, action);
    // 同步:
    // const res = enforcer.enforceSync(sub, obj, act);
 
    if (res) {
      return "ok";
      // 允许 alice 读取 data1
    } else {
      return "error";
      // 访问拒绝
    }
  }
}

控制类中提供casbin权限控制器的初始化,并提供权限查询方法getPermission();

  1. basic_model.conf 模型文件:
[request_definition]
r = sub, obj, act

[policy_definition]
p = sub, obj, act

[policy_effect]
e = some(where (p.eft == allow))

[matchers]
m = r.sub == p.sub && r.obj == p.obj && r.act == p.act

上述配置说明如下:

  • request_definition:部分用于request的定义,它明确了 e.Enforce(...) 函数中参数的含义。sub, obj, act 表示经典三元组: 访问实体 (Subject),访问资源 (Object) 和访问方法 (Action)

  • policy_definition:对policy的定义,可以理解为对上述request_definition中传入的参数的载体。例如:p = sub, obj, act就和r = sub, obj, act要一致。

  • policy_effect:策略效果的定义。 它确定如果多项政策规则与请求相符,是否应批准访问请求。例如:e = some(where (p.eft == allow))就表示只要有一个匹配的策略规则,那么就允许e = some(where (p.eft == allow)) && !some(where (p.eft == deny))就表示只要有一条匹配的策略规则,并且没有匹配拒绝的策略规则,那么就允许

  • matchers:是策略匹配程序的定义。匹配程序是表达式。它定义了如何根据请求评估策略规则。例如:m = r.sub == p.sub && r.obj == p.obj && r.act == p.act,表示请求中的主题(用户)、对象(带访问资源)和行动(操作权限)应该与政策规则中的匹配。

    关于模型语法的更多说明,请查看casbin模型语法

  1. basic_policy.csv 策略文件:
p, alice, data1, read
p, bob, data2, write

上述配置意味着:

  • alice可以读取data1
  • bob可以编写data2

3、执行

这时候我们发起POST请求/auth/getPermission后,在permission.ts控制器中查看结果,可以看到权限是true

image-20240502172702832

不同入参对应的权限判断结果如下:

Permission.getPermission("alice", "data1", "read");		//有权限:true
Permission.getPermission("alice", "data1", "write");	//无权限:false
Permission.getPermission("bob", "data2", "write");		//有权限:true
Permission.getPermission("bob", "data2", "read");		//无权限:false

4、动态添加策略

用户或者角色有什么对应角色,我们一般是存储在数据库中,那么我们就需要动态读取数据库的权限策略。node-casbin中是提供了addPolicy(user, resource, action)方法。

permission.ts 文件

import { Enforcer, newEnforcer } from "casbin";

export default class Permission {
  private static enforcerIns: Enforcer

  /**
   * 初始化权限实例
   */
  public static async init() {
    try {
      const path_prefix = "src/auth/permission";   //默认指向项目根目录,不需要:/
      this.enforcerIns = await newEnforcer(`${path_prefix}/basic_model.conf`, `${path_prefix}/basic_policy.csv`);

	  // 读取mysql数据,并在此处批量添加策略
      // await this.enforcerIns.addPolicy("bob", "data2", "read");		//单个添加
      await this.enforcerIns.addPolicies([["bob", "data2", "read"]]);	//批量添加
    } catch (error) {
      throw new Error(error?.message);
    }
  }

  /**
   * 获取权限
   * @param user 用户
   * @param resource 要访问的资源
   * @param action 权限:read
   */
  public static async getPermission(user: string = "alice", resource: string = "data1", action: string = "read") {
    const res = await this.enforcerIns.enforce(user, resource, action);
    if (res) {
      return "ok";
    } else {
      return "error";
    }
  }
}

5、分组模型和略测配置

# 模型
[request_definition]
r = sub, obj, act

[policy_definition]
p = sub, obj, act

[role_definition]
g = _, _

[policy_effect]
e = some(where (p.eft == allow))

[matchers]
m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act
#策略
p, alice, data1, read
p, bob, data2, write
p, data2_admin, data2, read
p, data2_admin, data2, write
g, alice, data2_admin

上述策略表示:

  • alicedata1read权限
  • bobdata2write权限
  • data2_admin角色对data2read权限
  • data2_admin角色对data2write权限
  • alice属于data2_admin角色

6、其他Api

  • enforcerIns.LoadModel():从*.conf文件重新加载模型
  • enforcerIns.LoadPolicy():从*.csv文件中重新加载策略
  • enforcerIns.SavePolicy():把当前策略重新写入*.csv文件
  • enforcerIns.removePolicies([["bob", "data2", "read"]]):批量删除策略
  • enforcerIns.removePolicy("alice", "data1", "read"):删除策略
  • enforcerIns.getRolesForUser("alice"):读取用户归属的角色

更多Api请查看:Adapters

;