一、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