Реалізація контролю доступу на основі ролей (RBAC) для керування публікаціями в блозі в додатку на NestJS.
Ми створимо три ролі: Адмін, Модератор та Користувач.
Кожна роль матиме певні дозволи для створення, редагування або видалення публікацій.
Крок перший: Перерахування ролей
// enums/roles.ts
export enum Role {
User = 'user',
Admin = 'admin',
Moderator = 'moderator',
}
Крок другий: Створення власного декоратора для ролей
// decorators/role.decorator.ts
import { SetMetadata } from '@nestjs/common';
export const ROLES_KEY = 'roles';
export const Roles = (...roles: string[]) => SetMetadata(ROLES_KEY, roles);
Крок третій: Реалізація охоронця ролей
RolesGuard
перевіряє, чи має користувач необхідні ролі для доступу до певного маршруту чи функції.
// src/guards/roles.guard.ts
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { ROLES_KEY } from '../decorators/role.decorator';
@Injectable()
export class RolesGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
const requiredRoles = this.reflector.getAllAndOverride(
ROLES_KEY,
[context.getHandler(), context.getClass()],
);
console.log('Required Roles:', requiredRoles);
if (!requiredRoles) {
return true;
}
const { user } = context.switchToHttp().getRequest();
console.log('Request User:', user);
if (!user || !user.role || user.role.length === 0) {
console.error('User or roles not defined in request or empty');
return false;
}
return requiredRoles.some((role) => user.role.includes(role));
}
}
Крок четвертий: Реалізація охоронця для токенів доступу
import { Injectable } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
@Injectable()
export class AccessTokenGuard extends AuthGuard('jwt') {}
Крок п’ятий: Визначення маршрутів на основі ролей
import {
Body,
Controller,
Delete,
Get,
Param,
Patch,
Post,
UseGuards,
} from '@nestjs/common'; // Імпортуємо необхідні декоратори NestJS
import { Roles } from 'src/common/decorators/role.decorator';
import { Role } from 'src/common/enums/role.enum';
import { AccessTokenGuard } from 'src/common/guards/accessToken.guard'; // Користувацький охоронець автентифікації
import { RolesGuard } from 'src/common/guards/role.guard';
import { CreateUserDto } from './dto/create-user.dto'; // DTO для створення користувача
import { UpdateUserDto } from './dto/update-user.dto'; // DTO для оновлення користувача
import { UsersService } from './users.service'; // Сервіс користувачів для обробки бізнес-логіки
@Controller('users') // Контролер для обробки маршрутів, пов'язаних з користувачами
export class UsersController {
constructor(private readonly usersService: UsersService) {} // Впровадження UsersService в контролер
@UseGuards(AccessTokenGuard) // Захищаємо маршрут POST за допомогою охоронця автентифікації
@Post() // HTTP метод POST для створення користувача
create(@Body() createUserDto: CreateUserDto) {
// Отримуємо createUserDto в тілі запиту для створення користувача
return this.usersService.create(createUserDto); // Делегуємо фактичну логіку в UsersService
}
@Get() // HTTP метод GET для отримання всіх користувачів
findAll() {
return this.usersService.findAll(); // Делегуємо логіку в UsersService для отримання всіх користувачів
}
@Get(':id') // HTTP метод GET для отримання користувача за його ID
findById(@Param('id') id: string) {
// Використовуємо декоратори param для доступу до ID користувача з URL
return this.usersService.findById(id); // Отримуємо користувача за його ID
}
@UseGuards(AccessTokenGuard, RolesGuard)
@Roles(Role.Admin, Role.Moderator)
@Patch(':id') // HTTP метод PATCH для оновлення даних користувача за його ID
update(@Param('id') id: string, @Body() updateUserDto: UpdateUserDto) {
// Отримуємо ID користувача та оновлені дані в тілі запиту
return this.usersService.update(id, updateUserDto); // Викликаємо сервіс для обробки оновлення користувача
}
@UseGuards(AccessTokenGuard, RolesGuard)
@Roles(Role.Admin) // Захищаємо маршрут DELETE за допомогою охоронця автентифікації
@Delete(':id') // HTTP метод DELETE для видалення користувача за його ID
remove(@Param('id') id: string) {
// Отримуємо ID користувача з параметрів URL
return this.usersService.remove(id); // Делегуємо логіку видалення до сервісу
}
}
Крок шостий: Отримання даних користувача з роллю з запиту
import { Injectable } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';
type JwtPayload = {
sub: string;
email: string;
role: string;
};
@Injectable()
export class AccessTokenStrategy extends PassportStrategy(Strategy, 'jwt') {
constructor() {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: process.env.JWT_ACCESS_SECRET,
});
}
validate(payload: JwtPayload) {
return payload;
}
}
Admin
може створювати, редагувати та видаляти пости.
Moderator
може створювати та редагувати пости.
User
може переглядати пости (за замовчуванням, якщо маршрут не захищений).
Переконайтеся, що ви додаєте роль до JWT під час входу користувача або реєстрації та використовуєте цю інформацію про роль у охоронцях та декораторах.
Перекладено з: Role Base Access Control In Nest JS