Skip to main content
Практика завдань

Як реалізувати контроль доступу на основі ролей (RBAC) у NestJS?

RBAC у NestJS

Контроль доступу на основі ролей (RBAC) обмежує доступ до ресурсів на основі ролей користувачів. NestJS надає Guards та декоратори для реалізації гнучкої авторизації.


1. Визначення ролей

typescript
export enum Role { User = 'user', Admin = 'admin', Moderator = 'moderator', SuperAdmin = 'superadmin', }

2. Декоратор ролей

typescript
import { SetMetadata } from '@nestjs/common'; export const ROLES_KEY = 'roles'; export const Roles = (...roles: Role[]) => SetMetadata(ROLES_KEY, roles);

3. Guard ролей

typescript
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common'; import { Reflector } from '@nestjs/core'; @Injectable() export class RolesGuard implements CanActivate { constructor(private reflector: Reflector) {} canActivate(context: ExecutionContext): boolean { const requiredRoles = this.reflector.getAllAndOverride<Role[]>( ROLES_KEY, [context.getHandler(), context.getClass()], ); if (!requiredRoles) return true; const { user } = context.switchToHttp().getRequest(); return requiredRoles.some((role) => user.roles?.includes(role)); } }

4. Застосування Guards

typescript
@Controller('admin') @UseGuards(JwtAuthGuard, RolesGuard) export class AdminController { @Get('dashboard') @Roles(Role.Admin, Role.SuperAdmin) getDashboard() { return { message: 'Адміністративна панель' }; } @Delete('users/:id') @Roles(Role.SuperAdmin) deleteUser(@Param('id') id: string) { return this.usersService.delete(id); } @Get('stats') @Roles(Role.Admin, Role.Moderator) getStats() { return this.statsService.getAll(); } }

5. Налаштування глобального Guard

typescript
@Module({ providers: [ { provide: APP_GUARD, useClass: JwtAuthGuard }, { provide: APP_GUARD, useClass: RolesGuard }, ], }) export class AppModule {}

Розширене: Доступ на основі дозволів

Для більш точного контролю використовуйте дозволи замість (або разом) ролей:

typescript
export enum Permission { ReadUsers = 'read:users', WriteUsers = 'write:users', DeleteUsers = 'delete:users', ReadPosts = 'read:posts', WritePosts = 'write:posts', ManageSettings = 'manage:settings', } const rolePermissions: Record<Role, Permission[]> = { [Role.User]: [Permission.ReadUsers, Permission.ReadPosts], [Role.Moderator]: [ Permission.ReadUsers, Permission.ReadPosts, Permission.WritePosts, ], [Role.Admin]: [ Permission.ReadUsers, Permission.WriteUsers, Permission.ReadPosts, Permission.WritePosts, Permission.ManageSettings, ], [Role.SuperAdmin]: Object.values(Permission), // Усі дозволи };
typescript
export const RequirePermissions = (...permissions: Permission[]) => SetMetadata('permissions', permissions); @Injectable() export class PermissionsGuard implements CanActivate { constructor(private reflector: Reflector) {} canActivate(context: ExecutionContext): boolean { const required = this.reflector.getAllAndOverride<Permission[]>( 'permissions', [context.getHandler(), context.getClass()], ); if (!required) return true; const { user } = context.switchToHttp().getRequest(); const userPermissions = user.roles .flatMap((role: Role) => rolePermissions[role] || []); return required.every((perm) => userPermissions.includes(perm)); } }

Використання:

typescript
@Controller('settings') export class SettingsController { @Get() @RequirePermissions(Permission.ManageSettings) getSettings() { return this.settingsService.getAll(); } }

Перевірка права власності на ресурс

typescript
@Injectable() export class OwnershipGuard implements CanActivate { constructor(private readonly postsService: PostsService) {} async canActivate(context: ExecutionContext): Promise<boolean> { const request = context.switchToHttp().getRequest(); const user = request.user; const postId = request.params.id; if (user.roles.includes(Role.Admin)) return true; const post = await this.postsService.findOne(postId); return post?.authorId === user.id; } } @Controller('posts') export class PostsController { @Put(':id') @UseGuards(OwnershipGuard) update(@Param('id') id: string, @Body() dto: UpdatePostDto) { return this.postsService.update(id, dto); } }

RBAC проти ABAC

ОсобливістьRBACABAC
Базується наРоляхАтрибутах (роль, час, IP тощо)
СкладністьПростаСкладна
ГнучкістьПомірнаДуже висока
Найкраще підходить дляБільшості веб-додатківПідприємств, відповідності

Найкраща практика: Починайте з простого RBAC. Додавайте перевірки на основі дозволів, коли гранулярність ролей недостатня. Додавайте guards власності для ресурсів, що належать користувачу. Використовуйте @Public() для відкритих кінцевих точок.

Коротка відповідь

Для співбесіди
Premium

Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.

Дочитали статтю?
Практика завдань