【NestJS】12 守卫(Guard):认证与权限控制实战

2929 字
15 分钟
【NestJS】12 守卫(Guard):认证与权限控制实战

这一片我们将学习 守卫(Guard),这是 NestJS 中用于权限控制的核心机制。

守卫决定一个请求是否应该被路由处理函数处理。它通常用于认证(Authentication)授权(Authorization)。认证是验证”你是谁”,授权是验证”你能做什么”。这两个概念是构建安全应用的基石 🔐

一、什么是守卫?#

守卫的定义 🎯#

守卫(Guard) 是决定一个请求是否应该被路由处理函数处理的类。它实现了 CanActivate 接口,返回布尔值来决定请求的命运。

守卫的核心逻辑:

  • 返回 true:请求继续处理,进入后续流程
  • 返回 false:请求被拒绝,通常返回 403 Forbidden 错误

📚 参考资源

守卫的执行时机 📍#

在 NestJS 的请求处理流程中,守卫处于关键位置:

请求 → 中间件 → 守卫 → 拦截器(前) → 管道 → 控制器

守卫在中间件之后、拦截器之前执行,确保只有通过验证的请求才能进入后续流程。

守卫的核心用途#

守卫在实际应用中主要用于三大场景:

  • 认证(Authentication):验证用户是否已登录,确认用户身份
  • 授权(Authorization):验证用户是否有权限访问特定资源
  • 角色控制(Role-Based Access Control):根据用户角色决定是否允许访问

二、创建第一个守卫#

使用 CLI 生成守卫#

NestJS CLI 提供了快速生成守卫的命令:

Terminal window
nest generate guard auth

或者使用简写:

Terminal window
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;
}

📚 参考资源

基本示例: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 第三方登录
  • 生物识别认证

📚 参考资源

完整的认证守卫实现#

创建一个功能完善的认证守卫:

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));
}
}

📚 参考资源

使用角色守卫#

@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(签名):用于验证令牌的完整性

📚 参考资源

安装依赖#

首先,安装必要的依赖包:

Terminal window
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 访问接口

Terminal window
curl http://localhost:3000/user/info \
-H "Authorization: Bearer <your-token>"

2. 测试未认证的请求

Terminal window
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验证”你能做什么”角色权限、资源访问控制

延伸学习资源 📚#

支持与分享

如果这篇文章对你有帮助,欢迎分享给更多人或赞助支持!

【NestJS】12 守卫(Guard):认证与权限控制实战
https://blog.fridolph.top/posts/2025-11-18__nest12-guard/
作者
Fridolph
发布于
2025-11-18
许可协议
CC BY-NC-SA 4.0

评论区

Profile Image of the Author
Fridolph
热爱 Coding、音乐和羽毛球的 90 后全栈工程师
公告
欢迎访问我的小站 ^_^ 我是昇哥,热爱Coding,喜爱音乐、羽毛球和摄影的 90后全栈工程师
分类
标签

文章目录