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

本文介绍了基于角色的访问控制(RBAC)原理及其在Node.js框架NestJS中的应用,通过node-casbin库实现权限管理,包括安装、模型配置、使用casbinAPI进行权限验证,以及动态添加策略。还展示了分组模型和更复杂的策略配置示例。
摘要由CSDN通过智能技术生成

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

  • 29
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
FastAPI本身不提供RBAC鉴权系统,但是可以通过第三方库来实现。这里介绍一种使用FastAPI和Pydantic实现RBAC鉴权系统的方法。 首先,需要定义用户模型和角色模型: ```python from typing import List from pydantic import BaseModel class User(BaseModel): id: int username: str password: str role_ids: List[int] = [] class Role(BaseModel): id: int name: str permissions: List[str] = [] ``` 然后,需要定义一个获取当前用户的函数,可以使用FastAPI的依赖注入功能实现: ```python from fastapi import Depends, HTTPException, status from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials from jwt import decode, exceptions from datetime import datetime, timedelta security = HTTPBearer() def get_current_user(credentials: HTTPAuthorizationCredentials = Depends(security)) -> User: token = credentials.credentials try: payload = decode(token, "SECRET_KEY", algorithms=["HS256"]) username = payload.get("sub") if username is None: raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid authentication credentials") user = get_user_by_username(username) if user is None: raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid username") return user except exceptions.DecodeError as e: raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid authentication credentials") except exceptions.ExpiredSignatureError as e: raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Token has expired") ``` 这个函数会从HTTP请求头中获取JWT令牌,并验证令牌的有效性和过期时间。如果验证通过,则返回当前用户对象。需要注意的是,这里的"SECRET_KEY"应该替换成真正的密钥。 接下来,需要定义一个检查权限的函数: ```python def check_permission(user: User, permission: str) -> bool: roles = get_roles_by_ids(user.role_ids) for role in roles: if permission in role.permissions: return True return False ``` 这个函数会检查当前用户是否拥有某个权限。需要根据用户的角色列表获取角色对象,然后检查角色是否拥有该权限。 最后,需要在路由定义中使用这些函数来进行鉴权: ```python from fastapi import FastAPI, Depends, HTTPException, status app = FastAPI() @app.get("/protected") def protected(user: User = Depends(get_current_user)): if not check_permission(user, "read_protected_data"): raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="You don't have permission to access this resource") return {"data": "This is protected data."} ``` 这个路由定义会使用get_current_user函数获取当前用户对象,然后使用check_permission函数检查是否拥有"read_protected_data"权限。如果没有权限,则返回HTTP 403 Forbidden错误。 需要注意的是,这里只是一个简单的示例,实际的RBAC鉴权系统可能会更加复杂。还需要考虑如何管理用户、角色和权限的数据,以及如何将鉴权逻辑集成到具体的业务逻辑中。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

IT飞牛

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值