Nestjs RBAC 權限控制管理實踐(一)

目前因爲在作 Nodejs 構架的遷移, 把原有的 typerx 的後端項目遷移到 NestJS 框架上來, 作到權限管理部分, 特和你們分享下。 項目地址:node

typerxgit

nestxgithub

NestJs 官方角色控制介紹

由於這篇文章主要是對權限管理部分對介紹, 因此暫定已經有了用戶身份知識的瞭解, 若想了解用戶登陸相關內容, 請參閱其餘相關文檔。數據庫

  1. Guards Guards 是一個註解式的守衛, 他描述了所修飾的控制器的訪問限制是什麼。他應該實現 CanActivate 這個接口。 Guards 有一個單一的職責就是決定請求是否能被路由處理。 *** 值得注意的是 Guards 處於每一個 middleware 以後, 但在 interceptor 和 pipe 以前。**後端

  2. 在瞭解權限以前咱們須要瞭解兩個概念一個是 Authentication , 一個是 Authorization, 你沒看花眼, 是的他們很像, 可是其實是不同的。bash

Authentication 與 Authorizationsession

Authentication 主要是身份檢查, 意思就像問你有沒身份證(有沒登陸)-> 401app

Authorization 主要是角色識別, 意思就像問你身份證的戶口是否是本地的(角色是什麼,有權限嗎) -> 403。框架

(官方文檔 Authorization guard# 和 Role-based authentication# 是否是有點反掉了呢?) auth.guard.tsasync

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());

複製代碼
  1. 通過上面的來回咱們已經有了 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 權限系統的需求

  1. 角色能夠自行定義。 圖示:

  1. 菜單下分多種類型的權限節點,如:讀寫控制等。 圖示:

  2. 角色能夠配置管理菜單下的權限配置節點。 圖示同上。

參考借鑑資源

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 看起來是比較流行的一個權限模塊了,各類語言都有, 提供的功能也比較全。

  1. 支持自定義請求的格式,默認的請求格式爲{subject, object, action};
  2. 具備訪問控制模型 model 和策略 policy 兩個核心概念;
  3. 支持 RBAC 中的多層角色繼承,不止主體能夠有角色,資源也能夠具備角色;
  4. 支持超級用戶,如 root 或 Administrator,超級用戶能夠不受受權策略的約束訪問任意資源;
  5. 支持多種內置的操做符,如 keyMatch,方便對路徑式的資源進行管理,如 /foo/bar 能夠映射到 /foo*;

對於 node-casbin 目前的集成到nestjs 的示例有: nest-casbin 和 nt-casbin, 但這兩個模塊都比較簡陋,只提供了簡單都service 的包裝調用

enforcer.enforce(sub, obj, act);
複製代碼

且沒有封裝註解標籤。 以上是一些準備和了解分析, 具體實現待續。

相關文章
相關標籤/搜索