守卫 (Guards) 与授权
守卫决定是否放行请求,常用于认证与授权。见 auth。
守卫负责在请求到达控制器处理函数之前,决定是否让该请求继续执行。常用于身份验证和授权(例如检查用户是否已登录、是否具有特定角色或权限)。守卫返回 true 表示允许访问,返回 false 或抛出异常则拒绝请求。
一、守卫的作用与接口
守卫实现 CanActivate 接口,该接口只有一个方法 canActivate(context: ExecutionContext),返回 boolean、Promise<boolean> 或 Observable<boolean>。
import { Injectable, CanActivate, ExecutionContext } from "@nestjs/common";
@Injectable()
export class AuthGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean {
const request = context.switchToHttp().getRequest();
const token = request.headers.authorization;
// 简单示例:验证 token 是否存在
return !!token;
}
}
如果守卫返回 false,NestJS 会抛出 ForbiddenException(可被异常过滤器捕获并转换为 403 响应)。
二、内置守卫
NestJS 核心库并没有提供具体的守卫实现,但官方提供了 @nestjs/passport 模块,它与 Passport.js 集成,实现了常见的身份认证守卫(如 JWT、Local、OAuth 等)。你可以基于它快速构建认证守卫。
例如使用 @nestjs/jwt 和 @nestjs/passport 创建 JwtAuthGuard:
import { Injectable } from "@nestjs/common";
import { AuthGuard } from "@nestjs/passport";
@Injectable()
export class JwtAuthGuard extends AuthGuard("jwt") {}
这个守卫会自动解析请求中的 JWT token 并验证其有效性,如果失败则返回 401。
在需要认证的路由上使用:
@UseGuards(JwtAuthGuard)
@Get('profile')
getProfile(@Req() req) {
return req.user;
}
三、使用守卫
1. 方法级或控制器级守卫
使用 @UseGuards() 装饰器,可以同时应用多个守卫(顺序执行,全部通过才放行)。
@Controller('users')
@UseGuards(AuthGuard) // 控制器级,所有方法生效
export class UsersController {
@Get()
findAll() { ... }
@Post()
@UseGuards(AdminGuard) // 方法级,覆盖或叠加控制器守卫(按顺序执行)
create() { ... }
}
2. 全局守卫
全局守卫应用于所有路由,可通过 app.useGlobalGuards 注册,或通过依赖注入方式(APP_GUARD 令牌)注册以便守卫使用其他服务。
方式一(无依赖注入):
// main.ts
app.useGlobalGuards(new AuthGuard());
方式二(支持依赖注入,推荐):
// 任意模块(通常是 AppModule)
import { Module } from "@nestjs/common";
import { APP_GUARD } from "@nestjs/core";
import { AuthGuard } from "./auth.guard";
@Module({
providers: [
{
provide: APP_GUARD,
useClass: AuthGuard,
},
],
})
export class AppModule {}
这样注册的全局守卫可以注入模块内的其他服务(如 ConfigService、UserService)。
四、守卫与中间件的区别
| 特性 | 中间件 | 守卫 |
|---|---|---|
| 职责 | 通用前置处理(日志、解析、跨域等) | 专门负责鉴权/授权 |
| 可否感知执行上下文 | 只能访问请求/响应对象,不知道哪个控制器/方法将被调用 | 可通过 ExecutionContext 获取目标控制器类、方法及其元数据 |
| 可否返回 false | 不能拦截请求(只能调用 next() 或抛异常) | 可以返回 false 来拒绝请求 |
| 依赖注入范围 | 只能通过模块的 configure 方法注册,支持依赖注入 | 与普通提供者相同,可灵活注入 |
关键点:守卫可以读取控制器或方法上通过 @SetMetadata 设置的自定义元数据(例如角色列表),从而做出更精细的权限判断。
五、通过反射获取自定义元数据
1. 使用 @SetMetadata() 设置元数据
@Controller('admin')
@SetMetadata('roles', ['admin'])
export class AdminController {
@Get()
@SetMetadata('permissions', ['read', 'write'])
getData() { ... }
}
2. 创建自定义装饰器(推荐)
通常我们封装 @Roles() 装饰器,隐藏 @SetMetadata 的实现细节。
import { SetMetadata } from "@nestjs/common";
export const Roles = (...roles: string[]) => SetMetadata("roles", roles);
使用:
@Roles('admin')
@Get()
findAll() { ... }
3. 守卫中读取元数据
通过 Reflector 工具类读取。
import { Injectable, CanActivate, ExecutionContext } from "@nestjs/common";
import { Reflector } from "@nestjs/core";
@Injectable()
export class RolesGuard implements CanActivate {
constructor(private 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; // 假设已经通过认证守卫挂载了 user 对象
return roles.some((role) => user.roles?.includes(role));
}
}
注意:Reflector.get 还可以通过 context.getClass() 获取控制器类上的元数据,可用于方法级与类级元数据合并。
六、守卫上下文:HTTP、GraphQL、WebSocket
ExecutionContext 是守卫的强大之处,它不仅能处理 HTTP 请求,还能用于 GraphQL 或 WebSocket 场景。
1. 区分上下文类型
通过 context.getType<'http' | 'ws' | 'rpc'>() 判断当前请求类型。
canActivate(context: ExecutionContext): boolean {
const type = context.getType();
if (type === 'http') {
const request = context.switchToHttp().getRequest();
// HTTP logic
} else if (type === 'ws') {
const client = context.switchToWs().getClient();
// WebSocket logic
} else if (type === 'rpc') {
const data = context.switchToRpc().getData();
// Microservice logic
}
return true;
}
2. GraphQL 场景
在 GraphQL 解析器中,守卫可以通过 context.getArgByIndex() 等获取参数,但更常见的是从 context 参数中获取请求信息。
// 在 GraphQL 模块中,执行上下文可以这样获取请求对象
const ctx = GqlExecutionContext.create(context);
const request = ctx.getContext().req;
七、综合示例:基于 JWT 认证 + 角色授权
// jwt-auth.guard.ts (基于 passport)
export class JwtAuthGuard extends AuthGuard('jwt') {}
// roles.guard.ts
export class RolesGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(ctx: ExecutionContext) {
const requiredRoles = this.reflector.get<string[]>('roles', ctx.getHandler());
if (!requiredRoles) return true;
const request = ctx.switchToHttp().getRequest();
const user = request.user;
return requiredRoles.some(role => user.roles?.includes(role));
}
}
// 控制器
@Controller('admin')
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles('admin')
export class AdminController {
@Get()
getData() { ... }
}
总结
| 概念 | 说明 |
|---|---|
| 守卫核心 | 实现 CanActivate 接口,返回布尔值决定是否放行 |
| 使用方式 | @UseGuards() 方法/控制器级;app.useGlobalGuards 或 APP_GUARD 全局 |
| 与中间件区别 | 守卫可感知执行上下文和自定义元数据,更适于授权 |
| 自定义元数据 | @SetMetadata → Reflector.get 读取;可封装装饰器简化 |
| 跨平台支持 | 通过 ExecutionContext 的 getType 和 switchTo* 适配 HTTP/WS/Microservice |
通过合理组合守卫和反射,可以实现灵活、可维护的权限控制体系。
参考文献
以下链接在编写时均可正常访问:
| 资料 | 说明 |
|---|---|
| NestJS 文档 | 官方 |
| Guards | 本章主题 |
| Request lifecycle | 执行顺序 |
相关文章
认证与授权
认证(Authentication):确认「你是谁」(如 JWT、Session)。 - 授权(Authorization):确认「你能做什么」(如 RBAC、策略检查)。
缓存
@nestjs/cache-manager 统一缓存 API,存储实现可插拔。
提供者与服务
Service 是最常见的 Provider,封装业务逻辑。见 module。
配置管理(Config 模块)
@nestjs/config 加载 .env 并提供 ConfigService。见 工程化 env。
数据库集成(以 TypeORM 为例)
Nest 通过 @nestjs/typeorm 等包集成 ORM;生产环境用 migration,慎用 synchronize。亦可选用 Prisma、MikroORM 等(见 官方 Database)。
定时任务
@nestjs/schedule 基于 cron 表达式调度任务。
Series
new
11 / 19