一、RBAC概述
RBAC鉴权,完整的英文描述是:Role-Based Access Control,中文意思是:基于角色(Role)的访问控制。这是一种广泛应用于计算机系统和网络安全领域的访问控制模型。
简单来说,就是通过将权限分配给➡角色,再将角色分配给➡用户,来实现对系统资源的访问控制。一个用户拥有若干角色,每一个角色拥有若干权限。这样,就构造成“用户-角色-权限”的授权模型。在这种模型中,用户与角色之间,角色与权限之间,一般者是多对多的关系。具体而言,RBAC模型定义了以下几个核心概心概念:
-
角色(Role):角色是指在系统中具有一组相关权限的抽象概念,代表了用户在特定上下文中的身份或职能,例如管理员、普通用户等。
-
权限(Permission):权限是指对系统资源进行操作的许可,如读取、写入、修改等。权限可以被分配给角色。
-
用户(User):用户是指系统的实际使用者,每个用户可以被分配一个或多个角色。
-
分配(Assignment):分配是指将角色与用户关联起来,以赋予用户相应的权限。
具体的鉴权逻辑如下图,也可以直接参考RBAC角色权限设计:
简单地说,一个用户拥有若干角色,每一个角色拥有若干权限。这样,就构造成“用户-角色-权限”的授权模型。在这种模型中,用户与角色之间,角色与权限之间,一般者是多对多的关系。
二、node-casbin使用模型
这里使用nestjs
脚手架做实战演练。
1、安装
# NPM
npm install casbin --save# Yarn
yarn add casbin# pnpm
pnpm add casbin
2、开始使用
node-casbin
使用模型文件和策略文件新建一个执行器,有关详细信息,请参阅模型部分,也可以直接查看源码中examples
目录下的模型文件:
-
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();}
}
-
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;}
}
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()
;
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模型语法
basic_policy.csv
策略文件:
p, alice, data1, read
p, bob, data2, write
上述配置意味着:
- alice可以读取data1
- bob可以编写data2
3、执行
这时候我们发起POST
请求/auth/getPermission
后,在permission.ts
控制器中查看结果,可以看到权限是true
不同入参对应的权限判断结果如下:
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
上述策略表示:
alice
对data1有read权限bob
对data2有write权限data2_admin
角色对data2有read权限data2_admin
角色对data2有write权限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