本文属于 NestJS BBS 实战教程,共 11 节,建议按顺序学习。
1. 这节课要实现的功能是什么
- 在“已登录”的基础上增加“有权限才可访问”。
- 定义角色模型:
user、moderator、admin。 - 实现管理员改角色、版主删帖等受控接口。
2. 这节课涉及的 NestJS 知识点有哪些(先讲概念)
2.1 先理解 Decorator:它在 NestJS 里是什么
Decorator(装饰器)可以理解成“给类、方法、参数附加信息的语法”。
Nest 会在运行时读取这些信息,然后决定这个类、这个方法、这个参数应该怎样被框架处理。
所以装饰器的价值不在于“写法简洁”,而在于:让你用声明式方式描述规则,而不是把规则硬写在业务代码里。
2.2 NestJS 中常见的 Decorator 类型
你可以先把 Nest 的装饰器分成四类:
- 类装饰器:描述一个类在框架中的角色,例如
@Module()、@Controller()、@Injectable() - 方法装饰器:描述一个方法该如何被处理,例如
@Get()、@Post()、@UseGuards() - 参数装饰器:描述参数从哪里来,例如
@Body()、@Param()、@Req()、@CurrentUser() - 元数据装饰器:给类或方法贴上额外规则,供 Guard、Interceptor 等运行时读取,例如
@Roles()
2.3 什么时候适合自己写 Decorator
适合自己写 Decorator 的场景通常有两类:
-
有重复的参数提取逻辑
例如每个接口都要写req.user,这时可以抽成@CurrentUser() -
有声明式规则要写在接口上
例如角色、权限、白名单、资源标签,这时可以抽成@Roles()、@Public()这种元数据装饰器
但要注意:Decorator 主要负责“声明”,不负责承载复杂执行逻辑。
@Roles('admin')负责声明“这个接口需要 admin”- 真正执行权限判断的是
RolesGuard
2.4 认证和授权的区别
- 认证(Authentication):你是谁(是否登录);
- 授权(Authorization):你能做什么(是否有权限)。
它们通常通过不同 Guard 完成,并且可以组合使用。
2.5 如何自定义 Guard
很多同学第一次接触 Guard 时,会以为只能用 Nest 现成的能力。其实不是。
只要你实现了 CanActivate 接口,并写好 canActivate() 方法,这个类就是一个自定义 Guard。
最小写法通常是这样:
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
@Injectable()
export class DemoGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean {
const request = context.switchToHttp().getRequest();
return !!request.headers['x-demo-token'];
}
}
这里的核心只有三件事:
@Injectable():让这个 Guard 能被 Nest 管理;implements CanActivate:声明这是一个 Guard;canActivate():定义“这个请求能不能继续执行”。
所以自定义 Guard 的本质不是“重新发明一套认证机制”,而是把你的访问控制规则写进 canActivate()。
2.6 什么时候适合自己写 Guard
适合自定义 Guard 的场景通常是:
- 判断某个请求头是否存在;
- 判断当前用户是否拥有某种业务权限;
- 判断资源是否属于当前用户;
- 判断是否命中白名单、灰度、开关策略。
换句话说,只要你有“访问前必须先判断”的规则,就可以考虑用 Guard。
2.7 @Roles() 与 Reflector
@Roles() 把角色要求写在接口上,RolesGuard 在运行时用 Reflector 读取元数据并做判断。
2.8 @CurrentUser() 的价值
把 request.user 提取为可复用装饰器,避免每个 Controller 重复写取值逻辑。
2.9 组合 Guard 的执行思路
典型顺序:JwtAuthGuard 先恢复用户,RolesGuard 再判断角色。
3. 实际开发中这些 NestJS 知识点被如何使用(详细代码)
3.1 src/auth/decorators/roles.decorator.ts
import { SetMetadata } from '@nestjs/common';
import type { UserRole } from '../../users/users.repository';
export const ROLES_KEY = 'roles';
export const Roles = (...roles: UserRole[]) => SetMetadata(ROLES_KEY, roles);
3.2 src/auth/guards/roles.guard.ts
RolesGuard 本身就是一个标准的自定义 Guard。
它并不是 Nest 内置的 Guard,而是项目自己实现的权限判断器:
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { ROLES_KEY } from '../decorators/roles.decorator';
import type { UserRole } from '../../users/users.repository';
@Injectable()
export class RolesGuard implements CanActivate {
constructor(private readonly reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
const requiredRoles = this.reflector.getAllAndOverride<UserRole[]>(ROLES_KEY, [
context.getHandler(),
context.getClass(),
]);
if (!requiredRoles || requiredRoles.length === 0) return true;
const request = context.switchToHttp().getRequest<{ user?: { role?: UserRole } }>();
const role = request.user?.role;
return !!role && requiredRoles.includes(role);
}
}
你可以把它拆成三步理解:
- 读取
@Roles()写入的方法元数据; - 从
request.user中取出当前用户角色; - 判断角色是否满足要求,最后返回
true或false。
3.3 src/auth/decorators/current-user.decorator.ts
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
import type { AuthUser } from '../types/auth-user';
export const CurrentUser = createParamDecorator(
(_data: unknown, context: ExecutionContext): AuthUser => {
const request = context.switchToHttp().getRequest<{ user: AuthUser }>();
return request.user;
},
);
3.4 src/users/users.controller.ts - 受控接口示例(管理员改角色)
@Patch(':id/role')
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles('admin')
async updateRole(
@Param('id', ParseIntPipe) id: number,
@Body() dto: UpdateUserRoleDto,
@CurrentUser() currentUser: { userId: number },
) {
if (currentUser.userId === id && dto.role !== 'admin') {
throw new ForbiddenException('Admin cannot remove own admin role');
}
return this.usersService.updateRole(id, dto.role);
}
3.5 src/threads/admin-threads.controller.ts - 受控接口示例(版主删帖)
@Controller('admin/threads')
@UseGuards(JwtAuthGuard, RolesGuard)
export class AdminThreadsController {
@Delete(':id')
@Roles('moderator', 'admin')
remove(@Param('id', ParseIntPipe) id: number, @CurrentUser() user: AuthUser) {
return this.threadsService.removeByModerator(id, user.userId);
}
}
4. 总结这节课知识点
- 你完成了认证到授权的升级。
- 你知道了 Guard 不只是内置能力,也可以通过实现
CanActivate自定义。 - 你掌握了元数据装饰器 + Guard 的权限实现模式。
- 你可以在不污染业务代码的前提下扩展复杂权限规则。
5. 每节课分支
lesson-06-guards-rbac- 远程仓库:
https://github.com/Shiuwn/nestjs-tutorial.git