Nestjs RBAC 許可權控制管理實踐(一)
目前由於在做 Nodejs 構架的遷移, 把原有的 typerx 的後端專案遷移到 NestJS 框架上來, 做到許可權管理部分, 特和大家分享下。 專案地址:
NestJs 官方角色控制介紹
因為這篇文章主要是對許可權管理部分對介紹, 所以暫定已經有了使用者身份知識的瞭解, 若想了解使用者登入相關內容, 請參閱其他相關文件。
-
Guards Guards 是一個註解式的守衛, 他描述了所修飾的控制器的訪問限制是什麼。他應該實現 CanActivate 這個介面。 Guards 有一個單一的職責就是決定請求是否能被路由處理。 *** 值得注意的是 Guards 處於每個 middleware 之後, 但在 interceptor 和 pipe 之前。**
-
在瞭解許可權之前我們需要了解兩個概念一個是 Authentication , 一個是 Authorization, 你沒看花眼, 是的他們很像, 但是實際上是不一樣的。
Authentication 與 Authorization
Authentication 主要是身份檢查, 意思就像問你有沒身份證(有沒登入)-> 401
Authorization 主要是角色識別, 意思就像問你身份證的戶口是不是本地的(角色是什麼,有許可權嗎) -> 403。
(官方文件 Authorization guard# 和 Role-based authentication# 是不是有點反掉了呢?) auth.guard.ts
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common'; import { Observable } from 'rxjs'; @Injectable() export class AuthGuard implements CanActivate { canActivate( context: ExecutionContext, ): boolean | Promise<boolean> | Observable<boolean> { const request = context.switchToHttp().getRequest(); return validateRequest(request); } } 複製程式碼
roles.guard.ts
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common'; import { Observable } from 'rxjs'; @Injectable() export class RolesGuard implements CanActivate { canActivate( context: ExecutionContext, ): boolean | Promise<boolean> | Observable<boolean> { return true; } } 複製程式碼
有了 AuthGuard 和 RolesGuard 之後,我們的控制器可以這麼寫, 當然你可以繫結多個用逗號隔開。
採用依賴注入的 RolesGuard
@Controller('cats') @UseGuards(RolesGuard) export class CatsController {} 複製程式碼
非採用依賴注入的, 還可以使用new的方式建立例項給他。
@Controller('cats') @UseGuards(new RolesGuard()) export class CatsController {} 複製程式碼
採用全域性方式, 可以省去每個地方去註解, 但不會對 gateways 和 microservices 起作用(待驗證下)
const app = await NestFactory.create(ApplicationModule); app.useGlobalGuards(new RolesGuard()); 複製程式碼
- 經過上面的來回我們已經有了 RolesGuard 了,但我們還需要把角色關聯到控制器。
直接看 cats.controller.ts 的寫法
@Post() @SetMetadata('roles', ['admin']) async create(@Body() createCatDto: CreateCatDto) { this.catsService.create(createCatDto); } 複製程式碼
這樣角色就關聯到控制器了,我們這樣就可以限定只有管理員才能訪問這個介面, 但是程式碼是不是有點醜, 不太簡潔對不對? 那我們換個方式:
@Post() @Roles('admin') async create(@Body() createCatDto: CreateCatDto) { this.catsService.create(createCatDto); } 複製程式碼
這樣是不是好多了? 這個主要我們加個下面的裝飾器就可以了
roles.decorator.ts
import { SetMetadata } from '@nestjs/common'; export const Roles = (...roles: string[]) => SetMetadata('roles', roles); 複製程式碼
roles.guard.ts 實現的細節
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common'; import { Observable } from 'rxjs'; import { Reflector } from '@nestjs/core'; @Injectable() export class RolesGuard implements CanActivate { constructor(private readonly reflector: Reflector) {} canActivate(context: ExecutionContext): boolean { const roles = this.reflector.get<string[]>('roles', context.getHandler()); // 從控制器註解中得到的角色組資訊。 if (!roles) { return true; } const request = context.switchToHttp().getRequest(); const user = request.user; const hasRole = () => user.roles.some((role) => roles.includes(role)); // 是否匹配到角色 return user && user.roles && hasRole(); } } 複製程式碼
這裡主要是從上下文中獲得 User 並從 User中取出角色和控制器中角色的註解看下是否有無交集匹配,如果有就放行。
總的來說官方提供的文件不是很多, 且這樣下來,整個方案也只是簡單的角色守護, 還無法完成複雜的許可權系統,但對一般性非靈活配置的系統也能滿足了。
nestx 許可權系統的需求
- 角色可以自行定義。 圖示:

-
選單下分多種型別的許可權節點,如:讀寫控制等。 圖示:
-
角色可以配置管理選單下的許可權配置節點。 圖示同上。
參考借鑑資源
nest-access-control
import { Get, Controller, UseGuards } from '@nestjs/common'; import { UserRoles, UseRoles, ACGuard } from 'nest-access-control'; import { AppService } from './app.service'; import { AuthGuard } from './auth.guard'; import { AppRoles } from 'app.roles'; @Controller() export class AppController { constructor(private readonly appService: AppService) {} @UseGuards(AuthGuard, ACGuard) @UseRoles({ resource: 'video', action: 'read', possession: 'any', }) @Get() root(@UserRoles() userRoles: any) { return this.appService.root(userRoles); } } 複製程式碼
Guard 註解方式比較有意思,分成資源、行為、許可權, 看樣子這個註解方式比較合乎我們的需要。
有個 RolesBuilder 的類可以建立定義:
// app.roles.ts export enum AppRoles { USER_CREATE_ANY_VIDEO = 'USER_CREATE_ANY_VIDEO', ADMIN_UPDATE_OWN_VIDEO = 'ADMIN_UPDATE_OWN_VIDEO', } export const roles: RolesBuilder = new RolesBuilder(); roles .grant(AppRoles.USER_CREATE_ANY_VIDEO) // define new or modify existing role. also takes an array. .createOwn('video') // equivalent to .createOwn('video', ['*']) .deleteOwn('video') .readAny('video') .grant(AppRoles.ADMIN_UPDATE_OWN_VIDEO) // switch to another role without breaking the chain .extend(AppRoles.USER_CREATE_ANY_VIDEO) // inherit role capabilities. also takes an array .updateAny('video', ['title']) // explicitly defined attributes .deleteAny('video'); 複製程式碼
目前看到的方式,主要是檔案的方式儲存的,我們需要放置到資料庫,這個模組可以參考, 待後續分析。
node-casbin
casbin 看起來是比較流行的一個許可權模組了,各種語言都有, 提供的功能也比較全。
- 支援自定義請求的格式,預設的請求格式為{subject, object, action};
- 具有訪問控制模型 model 和策略 policy 兩個核心概念;
- 支援 RBAC 中的多層角色繼承,不止主體可以有角色,資源也可以具有角色;
- 支援超級使用者,如 root 或 Administrator,超級使用者可以不受授權策略的約束訪問任意資源;
- 支援多種內建的操作符,如 keyMatch,方便對路徑式的資源進行管理,如 /foo/bar 可以對映到 /foo*;
對於 node-casbin 目前的整合到nestjs 的示例有: nest-casbin 和 nt-casbin, 但這兩個模組都比較簡陋,只提供了簡單都service 的包裝呼叫
enforcer.enforce(sub, obj, act); 複製程式碼
且沒有封裝註解標籤。 以上是一些準備和了解分析, 具體實現待續。