这一片我们将学习 守卫(Guard),这是 NestJS 中用于权限控制的核心机制。
守卫决定一个请求是否应该被路由处理函数处理。它通常用于认证(Authentication)和授权(Authorization)。认证是验证"你是谁",授权是验证"你能做什么"。这两个概念是构建安全应用的基石 🔐
一、什么是守卫?
守卫的定义 🎯
守卫(Guard) 是决定一个请求是否应该被路由处理函数处理的类。它实现了 CanActivate 接口,返回布尔值来决定请求的命运。
守卫的核心逻辑:
- 返回
true:请求继续处理,进入后续流程 - 返回
false:请求被拒绝,通常返回403 Forbidden错误
📚 参考资源:
- NestJS Guards 官方文档 - 官方完整指南
- JWT 官方网站 - JSON Web Token 标准
守卫的执行时机 📍
在 NestJS 的请求处理流程中,守卫处于关键位置:
请求 → 中间件 → 守卫 → 拦截器(前) → 管道 → 控制器守卫在中间件之后、拦截器之前执行,确保只有通过验证的请求才能进入后续流程。
守卫的核心用途
守卫在实际应用中主要用于三大场景:
- 认证(Authentication):验证用户是否已登录,确认用户身份
- 授权(Authorization):验证用户是否有权限访问特定资源
- 角色控制(Role-Based Access Control):根据用户角色决定是否允许访问
二、创建第一个守卫
使用 CLI 生成守卫
NestJS CLI 提供了快速生成守卫的命令:
nest generate guard auth或者使用简写:
nest g guard auth这会创建 src/auth/auth.guard.ts 文件。
基本守卫实现
让我们看看生成的文件结构:
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
@Injectable()
export class AuthGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean {
// 守卫逻辑
return true;
}
}ExecutionContext 详解
ExecutionContext 提供了当前执行上下文的丰富信息:
canActivate(context: ExecutionContext): boolean {
// 获取 HTTP 上下文
const request = context.switchToHttp().getRequest();
const response = context.switchToHttp().getResponse();
// 可以访问请求的所有信息
return true;
}📚 参考资源:
- ExecutionContext API 文档 - 执行上下文详解
基本示例:Token 验证守卫
创建一个简单的认证守卫,验证请求头中的 Token:
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
@Injectable()
export class AuthGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean {
const request = context.switchToHttp().getRequest();
const token = request.headers.authorization;
if (!token) {
return false; // 未提供 token,拒绝请求
}
// 验证 token(简化示例)
return this.validateToken(token);
}
private validateToken(token: string): boolean {
// 实际项目中应该验证 JWT
return token.startsWith('Bearer ');
}
}三、使用守卫
控制器级别守卫
使用 @UseGuards() 装饰器应用到整个控制器:
@Controller('user')
@UseGuards(AuthGuard) // 所有路由都需要认证
export class UserController {
@Get()
findAll() {
return '所有用户';
}
}方法级别守卫
也可以只应用到特定方法:
@Controller('user')
export class UserController {
@Get()
@UseGuards(AuthGuard) // 只有这个方法需要认证
findAll() {
return '所有用户';
}
@Get('public')
getPublic() {
return '公开信息'; // 不需要认证
}
}全局守卫
在 app.module.ts 中注册全局守卫,对所有路由生效:
import { APP_GUARD } from '@nestjs/core';
import { AuthGuard } from './auth/auth.guard';
@Module({
providers: [
{
provide: APP_GUARD,
useClass: AuthGuard,
},
],
})
export class AppModule {}多个守卫组合
可以同时应用多个守卫,它们会按顺序执行:
@UseGuards(AuthGuard, RolesGuard) // 先认证,再验证角色
export class UserController {
// 所有守卫都返回 true 时,请求才会继续
}四、认证守卫实现
什么是认证?
认证(Authentication) 是验证用户身份的过程,确认"你是谁"。常见的认证方式包括:
- 用户名密码登录
- Token 认证(如 JWT)
- OAuth 第三方登录
- 生物识别认证
📚 参考资源:
- OAuth 2.0 官方文档 - OAuth 认证标准
- MDN - HTTP 认证 - HTTP 认证机制
完整的认证守卫实现
创建一个功能完善的认证守卫:
import {
CanActivate,
ExecutionContext,
Injectable,
UnauthorizedException,
} from '@nestjs/common';
import { Request } from 'express';
@Injectable()
export class AuthGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean {
const request = context.switchToHttp().getRequest<Request>();
const token = this.extractTokenFromHeader(request);
if (!token) {
throw new UnauthorizedException('未提供认证令牌');
}
// 验证 token(实际项目中应该验证 JWT)
if (!this.validateToken(token)) {
throw new UnauthorizedException('认证令牌无效');
}
// 将用户信息附加到请求对象,供后续使用
request.user = this.getUserFromToken(token);
return true;
}
private extractTokenFromHeader(request: Request): string | undefined {
const [type, token] = request.headers.authorization?.split(' ') ?? [];
return type === 'Bearer' ? token : undefined;
}
private validateToken(token: string): boolean {
// 实际的 token 验证逻辑
// 这里简化处理
return token.length > 10;
}
private getUserFromToken(token: string): any {
// 从 token 中提取用户信息
// 实际项目中应该解析 JWT
return { id: 1, name: '测试用户', roles: ['user'] };
}
}在控制器中使用
@Controller('user')
@UseGuards(AuthGuard)
export class UserController {
@Get('info')
getInfo(@Request() req: any) {
// req.user 包含从守卫中注入的用户信息
return req.user;
}
}五、授权守卫实现
什么是授权?
授权(Authorization) 是验证用户是否有权限访问资源的过程,确认"你能做什么"。授权发生在认证之后。
常见的授权模型:
- RBAC(Role-Based Access Control):基于角色的访问控制
- ABAC(Attribute-Based Access Control):基于属性的访问控制
- ACL(Access Control List):访问控制列表
实现角色守卫
创建一个基于角色的授权守卫:
import {
CanActivate,
ExecutionContext,
Injectable,
SetMetadata,
} from '@nestjs/common';
import { Reflector } from '@nestjs/core';
// 定义角色装饰器
export const Roles = (...roles: string[]) => SetMetadata('roles', roles);
@Injectable()
export class RolesGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
// 获取路由处理器上的角色元数据
const requiredRoles = this.reflector.get<string[]>(
'roles',
context.getHandler(),
);
if (!requiredRoles) {
return true; // 没有角色要求,允许访问
}
const request = context.switchToHttp().getRequest();
const user = request.user;
if (!user) {
return false; // 用户未认证
}
// 检查用户是否拥有所需角色
return requiredRoles.some((role) => user.roles?.includes(role));
}
}📚 参考资源:
- Reflector API 文档 - 元数据反射
使用角色守卫
@Controller('admin')
@UseGuards(AuthGuard, RolesGuard) // 先认证,再验证角色
export class AdminController {
@Get('users')
@Roles('admin') // 只有 admin 角色可以访问
getUsers() {
return '所有用户列表';
}
@Get('stats')
@Roles('admin', 'moderator') // admin 或 moderator 角色可以访问
getStats() {
return '统计数据';
}
}六、JWT 认证守卫实现
什么是 JWT?
JWT(JSON Web Token) 是一种开放标准(RFC 7519),用于在各方之间安全地传输信息。JWT 由三部分组成:
- Header(头部):包含令牌类型和加密算法
- Payload(负载):包含用户信息和其他数据
- Signature(签名):用于验证令牌的完整性
📚 参考资源:
安装依赖
首先,安装必要的依赖包:
pnpm add @nestjs/jwt@11.0.0 @nestjs/passport@11.0.5 passport@0.7.0 passport-jwt@4.0.1
pnpm add -D @types/passport-jwt@4.0.1配置 JWT 模块
在 app.module.ts 中配置 JWT 模块:
import { JwtModule } from '@nestjs/jwt';
import { JwtStrategy } from './auth/jwt.strategy';
@Module({
imports: [
JwtModule.register({
secret: 'sunday-secret-key', // 应该从环境变量读取
signOptions: { expiresIn: '24h' },
}),
],
providers: [JwtStrategy],
})
export class AppModule {}推荐使用异步配置,从环境变量读取密钥:
JwtModule.registerAsync({
imports: [ConfigModule],
useFactory: async (configService: ConfigService) => ({
secret: configService.get<string>('JWT_SECRET'),
signOptions: { expiresIn: '24h' },
}),
inject: [ConfigService],
}),创建 JWT Strategy
创建 src/auth/jwt.strategy.ts 文件:
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';
import { ConfigService } from '@nestjs/config';
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor(private configService: ConfigService) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), // 从 Bearer Token 中提取
ignoreExpiration: false, // 不忽略过期时间
secretOrKey: configService.get<string>('JWT_SECRET') || 'sunday-secret-key',
});
}
async validate(payload: any) {
if (!payload.userId) {
throw new UnauthorizedException('Token 无效');
}
// 返回的对象会被附加到 request.user
return {
userId: payload.userId,
username: payload.username,
roles: payload.roles || ['user'],
};
}
}创建 JWT 认证守卫
创建 src/auth/jwt-auth.guard.ts 文件:
import { Injectable } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {}使用 JWT 认证守卫
@Controller('user')
@UseGuards(JwtAuthGuard) // 使用 JWT 认证
export class UserController {
@Get('info')
getInfo(@Request() req: any) {
// req.user 包含从 JWT 中解析的用户信息
return req.user;
}
}七、执行上下文(ExecutionContext)
ExecutionContext 详解
ExecutionContext 是守卫中最重要的参数,提供了当前执行上下文的完整信息。
获取请求和响应对象
canActivate(context: ExecutionContext): boolean {
const ctx = context.switchToHttp();
const request = ctx.getRequest(); // 获取请求对象
const response = ctx.getResponse(); // 获取响应对象
// 访问请求信息
console.log(request.method); // GET, POST, etc.
console.log(request.url); // /user/info
console.log(request.headers); // 请求头
return true;
}获取路由信息
canActivate(context: ExecutionContext): boolean {
const handler = context.getHandler(); // 获取处理方法
const controller = context.getClass(); // 获取控制器类
console.log(handler.name); // 方法名
console.log(controller.name); // 控制器名
return true;
}获取元数据
使用 Reflector 获取通过装饰器设置的元数据:
import { Reflector } from '@nestjs/core';
constructor(private reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
// 获取方法上的角色元数据
const roles = this.reflector.get<string[]>('roles', context.getHandler());
// 获取控制器上的元数据
const isPublic = this.reflector.get<boolean>('isPublic', context.getClass());
return true;
}八、实战:完整的权限验证系统
需求分析 📋
创建一个完整的权限验证系统,包含以下功能:
- JWT 认证守卫:验证用户身份
- 角色守卫:验证用户权限
- 在控制器中组合使用
完整实现
第一步:配置 JWT 模块
在 app.module.ts 中配置(参考前面的代码)。
第二步:创建 JWT Strategy
创建 src/auth/jwt.strategy.ts(参考前面的代码)。
第三步:创建 JWT 认证守卫
创建 src/auth/jwt-auth.guard.ts(参考前面的代码)。
第四步:创建角色守卫
// src/auth/roles.guard.ts
import {
CanActivate,
ExecutionContext,
Injectable,
SetMetadata,
} from '@nestjs/common';
import { Reflector } from '@nestjs/core';
export const Roles = (...roles: string[]) => SetMetadata('roles', roles);
@Injectable()
export class RolesGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
const requiredRoles = this.reflector.get<string[]>(
'roles',
context.getHandler(),
);
if (!requiredRoles) {
return true;
}
const request = context.switchToHttp().getRequest();
const user = request.user;
if (!user) {
return false;
}
return requiredRoles.some((role) => user.roles?.includes(role));
}
}第五步:在控制器中使用
import { Controller, Get, UseGuards, Request } from '@nestjs/common';
import { JwtAuthGuard } from '../auth/jwt-auth.guard';
import { RolesGuard, Roles } from '../auth/roles.guard';
@Controller('user')
@UseGuards(JwtAuthGuard) // 所有路由都需要认证
export class UserController {
@Get('info')
getInfo(@Request() req: any) {
return req.user;
}
@Get('admin')
@UseGuards(RolesGuard)
@Roles('admin')
getAdminInfo(@Request() req: any) {
// 只有 admin 角色可以访问
return { message: '管理员信息', user: req.user };
}
}测试验证 ✅
1. 使用 Token 访问接口
curl http://localhost:3000/user/info \
-H "Authorization: Bearer <your-token>"2. 测试未认证的请求
curl http://localhost:3000/user/info应该返回 401 Unauthorized 错误。
九、守卫的最佳实践
1. 分离认证和授权 🔐
// ✅ 推荐:分离关注点
@UseGuards(JwtAuthGuard, RolesGuard)
export class AdminController {}2. 使用装饰器提高可读性 📝
// ✅ 推荐:清晰明了
@Roles('admin')
@Get('admin')
getAdmin() {
return '管理员信息';
}3. 提供友好的错误信息 💬
// ✅ 推荐:友好的错误信息
if (!user) {
throw new UnauthorizedException('请先登录后再访问');
}
if (!hasPermission) {
throw new ForbiddenException('您没有权限访问此资源');
}4. 使用全局守卫 🌐
// ✅ 推荐:全局认证
@Module({
providers: [
{
provide: APP_GUARD,
useClass: JwtAuthGuard,
},
],
})
export class AppModule {}十、常见问题排查
问题 1:守卫未执行
可能原因:守卫未注册或未使用 @UseGuards() 装饰器
解决方案:
// ✅ 确保守卫已注册
@Module({
providers: [AuthGuard],
})问题 2:Token 验证失败
可能原因:Token 格式不正确或 Secret 不匹配
解决方案:检查 Token 格式和 Secret 配置
问题 3:用户信息未注入
可能原因:守卫的 validate 方法未返回用户信息
解决方案:确保返回用户信息对象
总结 🎯
这节课我们系统学习了 NestJS 守卫的核心概念和实战应用。
核心知识点 ✅
- ✅ 理解了守卫的定义:决定请求是否应该被处理的类
- ✅ 掌握了守卫的执行时机:在中间件之后、拦截器之前
- ✅ 学习了如何创建守卫:实现
CanActivate接口 - ✅ 实现了认证守卫:验证用户身份
- ✅ 实现了授权守卫:验证用户权限
- ✅ 学习了 JWT 认证:使用 Passport 和 JWT
- ✅ 了解了 ExecutionContext:获取执行上下文信息
认证 vs 授权对比 📊
| 概念 | 英文 | 作用 | 示例 |
|---|---|---|---|
| 认证 | Authentication | 验证"你是谁" | 用户登录、Token 验证 |
| 授权 | Authorization | 验证"你能做什么" | 角色权限、资源访问控制 |
延伸学习资源 📚
- 本文链接:https://fridolph.top/posts/2025-11-18__nest12-guard
- 版权声明:本博客所有文章除特别声明外,均默认采用 CC BY-NC-SA 许可协议。