Практика управления разрешениями Nestjs RBAC (1)

NestJS
Практика управления разрешениями Nestjs RBAC (1)

В настоящее время из-за переноса фреймворка Nodejs исходный серверный проект typerx был перенесен в фреймворк NestJS, и часть управления разрешениями была завершена, и я хотел бы поделиться им с вами. адрес проекта:

typerx

nestx

Введение в официальное управление персонажем NestJs

Поскольку эта статья в основном представляет собой введение в часть управления разрешениями, поэтому предварительно у вас уже есть представление об идентификации пользователя.Если вы хотите узнать о входе пользователя в систему, обратитесь к другим связанным документам.

  1. GuardsОхранники — это аннотированные охранники, описывающие ограничения доступа декорированного контроллера. Он должен реализовать интерфейс CanActivate. Охранники несут единственную ответственность за принятие решения о том, может ли запрос быть маршрутизирован или нет. *** Стоит отметить, что Guards идут после каждого промежуточного ПО, но до перехватчиков и каналов. **

  2. Прежде чем понять разрешения, нам нужно понять два понятия, одно — Аутентификация, а другое — Авторизация.Вы не запутались.Да, они очень похожи, но на самом деле они разные.

Аутентификация и авторизация

Аутентификация — это в основном проверка личности, что означает, например, вопрос о том, есть ли у вас удостоверение личности (вы вошли в систему) -> 401

Авторизация в основном связана с идентификацией роли, что означает запрос на то, является ли учетная запись вашей идентификационной карты локальной (какая роль, есть ли у вас разрешение) -> 403.

(Являются ли официальные документы Защита авторизации # и аутентификация на основе ролей # немного перевернутыми?) 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 {}

Вместо внедрения зависимостей вы также можете использовать новый метод для создания экземпляра для него.

@Controller('cats')
@UseGuards(new RolesGuard())
export class CatsController {}

Глобально можно сохранить каждое место для аннотирования, но на шлюзах и микросервисах это не сработает (проверяется)

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

Здесь в основном нужно получить пользователя из контекста и вынуть из пользователя аннотации роли и роли в контроллере, чтобы посмотреть, есть ли совпадение пересечения, и если да, то отпустить.

В общем, официально предоставленных документов не так много, и, таким образом, все решение представляет собой лишь простую защиту ролей, и оно не может дополнить сложную систему разрешений, но также может удовлетворить общую негибкую систему конфигурации.

Требования к системе разрешений 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. Поддержка пользовательского формата запроса, формат запроса по умолчанию: {тема, объект, действие};
  2. Он имеет две основные концепции модели модели управления доступом и политики политики;
  3. Поддержка многоуровневого наследования ролей в RBAC, роли могут быть не только у субъектов, но и у ресурсов;
  4. Поддержка суперпользователей, таких как root или Administrator, суперпользователи могут получать доступ к произвольным ресурсам без ограничений политиками авторизации;
  5. Поддержка различных встроенных операторов, таких как keyMatch, для облегчения управления ресурсами на основе пути, например, /foo/bar может быть сопоставлен с /foo*;

Текущие примеры интеграции вnesjs для node-casbin: nest-casbin и nt-casbin, но эти два модуля относительно просты и обеспечивают только простые вызовы сервисных пакетов.

enforcer.enforce(sub, obj, act);

И нет тега аннотации пакета. Вышеизложенное является некоторой подготовкой и анализом понимания, и конкретная реализация будет продолжена.