【NestJS】12 守卫(Guard):认证与权限控制实战
这一片我们将学习 守卫(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.1pnpm 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(参考前面的代码)。
第四步:创建角色守卫
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 | 验证”你能做什么” | 角色权限、资源访问控制 |
延伸学习资源 📚
支持与分享
如果这篇文章对你有帮助,欢迎分享给更多人或赞助支持!