如何在NestJS中为自定义Role装饰器注入服务实现RBAC?
在NestJS中实现基于数据库角色的RBAC(服务注入+装饰器+守卫)
首先明确:自定义装饰器本身无法直接注入服务,因为装饰器在类/方法初始化阶段就会执行,此时Nest的依赖注入容器还未完成初始化。正确的做法是用「装饰器标记角色需求」+「守卫注入服务验证角色」的组合方案,具体步骤如下:
1. 创建Role装饰器(仅存储元数据)
这个装饰器的作用是给控制器/方法打上需要的角色标签,不需要和服务交互:
import { SetMetadata } from '@nestjs/common'; export const ROLES_KEY = 'roles'; export const Role = (...roles: string[]) => SetMetadata(ROLES_KEY, roles);
2. 创建RBAC守卫(注入服务验证角色)
在守卫里注入你的用户角色服务,从数据库拉取用户实际角色,再和装饰器标记的角色做对比:
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common'; import { Reflector } from '@nestjs/core'; import { ROLES_KEY } from './role.decorator'; import { UserRoleService } from './user-role.service'; // 你的角色服务 @Injectable() export class RolesGuard implements CanActivate { constructor( private reflector: Reflector, private userRoleService: UserRoleService // 注入角色服务 ) {} async canActivate(context: ExecutionContext): Promise<boolean> { // 获取装饰器标记的需要的角色 const requiredRoles = this.reflector.getAllAndOverride<string[]>(ROLES_KEY, [ context.getHandler(), context.getClass(), ]); if (!requiredRoles) { return true; // 没有标记角色则允许访问 } // 从请求中获取当前用户(假设你已经通过AuthGuard拿到了用户信息,比如JWT解析后的用户ID) const request = context.switchToHttp().getRequest(); const userId = request.user.id; // 调用服务从数据库获取用户实际角色 const userRoles = await this.userRoleService.getUserRoles(userId); // 验证用户是否拥有所需角色 return requiredRoles.some(role => userRoles.includes(role)); } }
3. 角色服务示例(从数据库获取角色)
你的UserRoleService需要实现从数据库查询用户角色的逻辑,比如:
import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { UserRole } from './user-role.entity'; // 你的用户角色实体 @Injectable() export class UserRoleService { constructor( @InjectRepository(UserRole) private userRoleRepository: Repository<UserRole> ) {} async getUserRoles(userId: number): Promise<string[]> { // 从数据库查询用户关联的角色,返回角色名称数组 const userRoles = await this.userRoleRepository.find({ where: { userId }, relations: ['role'], }); return userRoles.map(ur => ur.role.name); } }
4. 控制器中使用装饰器和守卫
在控制器或方法上同时使用@Role()装饰器和@UseGuards()守卫:
import { Controller, Get, UseGuards } from '@nestjs/common'; import { AuthGuard } from '@nestjs/passport'; // 假设你用JWT认证,先通过AuthGuard拿到用户 import { Role } from './role.decorator'; import { RolesGuard } from './roles.guard'; @Controller('admin') @UseGuards(AuthGuard('jwt'), RolesGuard) // 先过认证守卫,再过RBAC守卫 export class AdminController { @Get('dashboard') @Role('admin', 'super-admin') // 需要admin或super-admin角色才能访问 getDashboard() { return 'Admin Dashboard'; } @Get('settings') @Role('super-admin') // 仅允许super-admin访问 getSettings() { return 'Super Admin Settings'; } }
关键注意点
- 装饰器只负责传递元数据,不要尝试在装饰器里注入服务,这是Nest的生命周期决定的。
- 确保
AuthGuard已经正确解析用户信息并挂载到request.user上,这样RBAC守卫才能拿到用户ID去查询角色。 - 如果需要全局启用RBAC守卫,可以在
AppModule的providers里配置:
这样就不用每个控制器都加{ provide: APP_GUARD, useClass: RolesGuard, }@UseGuards(RolesGuard)了。
内容的提问来源于stack exchange,提问作者Kasun Akalanka
相关产品推荐
相关产品推荐

