MHUNTER.cn

December 3, 2025

NestJS + JWT :打造全栈认证系统的最佳实践

typeScriptNestJS6.0 min to read

从零到一:手把手带你用 NestJS + Prisma + JWT 打造安全可靠的用户认证系统!NestJS认证系统封面

前言

最近在学习和实践后端开发,用 NestJS 框架构建了一个完整的用户认证系统。这个系统不仅实现了基础的登录注册功能,还整合了 JWT 认证、验证码校验等安全特性。今天就跟大家分享一下这个项目的开发过程和核心代码实现,希望对大家有所帮助!

技术栈介绍

本项目主要使用了以下技术:

核心功能模块讲解

1. 模块化设计:登录模块结构

首先看一下我们的登录模块结构:

CODE
// login.module.ts import { Module } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import { JwtModule } from '@nestjs/jwt'; import { PrismaService } from 'src/db'; import { LoginController } from './login.controller'; import { LoginPipe } from './login.pipe'; import { LoginService } from './login.service'; import { JwtStrategy } from './jstStrategy'; @Module({   imports: [     JwtModule.registerAsync({       inject: [ConfigService],       global: true,       useFactory: (configService: ConfigService) => ({         secret: configService.get('TOKEN_SECRET'),         signOptions: { expiresIn: '60s' },       }),     }),   ],   controllers: [LoginController],   providers: [LoginService, PrismaService, LoginPipe, JwtStrategy], }) export class LoginModule {}

这段代码展示了 NestJS 的模块化设计理念。我们通过 @Module 装饰器定义了登录模块,并引入了 JWT 模块来处理令牌生成和验证。特别注意的是: 使用 registerAsync 方法异步配置 JWT,便于从环境变量获取密钥 将 JWT 设置为全局模块,让整个应用都能使用 注入了登录控制器、服务、数据库服务和参数校验管道 > 小技巧:将敏感信息如 Token 密钥放在环境变量中,避免硬编码到代码中,大大提高了项目的安全性。

2. 参数校验:DTO 和验证管道

我们使用 DTO (Data Transfer Object) 来定义和校验请求参数:

CODE
// loginAuth.dto.ts import { IsNotEmpty, IsString } from 'class-validator'; export class LoginAuthDto {   @IsNotEmpty()   @IsString({ message: '用户名不能为空' })   name: string;      @IsNotEmpty()   @IsString({ message: '密码不能为空' })   password: string; }

简单几行代码就完成了参数校验的定义,配合 ValidationPipe 全局管道,可以自动拦截不合法的请求。这种声明式的校验方式比手动在代码中写判断要简洁得多,而且错误提示也更加友好。

3. 控制器层:处理 HTTP 请求

CODE
// login.controller.ts 的部分代码 @Controller('auth') export class LoginController {   constructor(     private readonly loginService: LoginService,     private readonly prisma: PrismaService,   ) {}   @Get('svg')   @Header('Content-Type', 'image/svg+xml')   async creactSvg(@Req() req: Request) {     return this.loginService.creactSvg(req);   }   @Post('login')   async userLogin(@Body() body: any, @Query() query: any, @Req() req: Request) {     return await this.loginService.processLogin(body, query);   }      @Get('all')   @Auth()  // 自定义认证装饰器   async getAll(@User() user: any) {  // 自定义用户信息提取装饰器     console.log('user', user);     return this.prisma.user.findMany();   } }

控制器层负责接收和响应 HTTP 请求,主要实现了: 生成验证码 SVG 图片 处理用户注册和登录 提供需要认证的 API 接口(使用 @Auth() 自定义装饰器) > 实战经验:使用自定义装饰器可以大大简化代码,提高可读性。如 @Auth() 装饰器替代了直接使用 @UseGuards(),@User() 装饰器则可以直接获取当前登录用户信息。

4. 服务层:核心业务逻辑

服务层包含了最核心的业务逻辑,下面重点看一下用户认证相关的代码:

CODE
// login.service.ts 的部分代码 @Injectable() export class LoginService {   constructor(     private readonly prisma: PrismaService,     private readonly jwtService: JwtService,   ) {}      // 生成 JWT 令牌   async generateToken({ username, id }: User) {     const token = await this.jwtService.signAsync({ username, sub: id });     console.log('生成的token:', token);     return token;   }      // 查找用户并验证密码   async findUserByUsername(body: LoginAuthDto) {     const user = await this.prisma.user.findUnique({       where: { name: body.name },     });     if (!user) {       throw new UnauthorizedException('用户不存在');     }     // 验证密码     if (!(await verify(user.password, body.password))) {       throw new UnauthorizedException('密码错误');     }          // 返回安全的用户信息(排除密码)     const { password, ...result } = user;     return result;   }      // 处理登录逻辑   async processLogin(body: any, query: any) {     try {       const loginData = {         name: body.name || query.name,         password: body.password || query.password,       };       if (!loginData.name || !loginData.password) {         throw new UnauthorizedException('用户名和密码不能为空');       }       const user = await this.findUserByUsername(loginData);       const token = await this.generateToken({         id: user.id,         username: user.name,       });       return {         success: true,         message: '登录成功',         code: 200,         user,         token,       };     } catch (error) {       // 处理异常     }   } }

服务层代码实现了: 用 JWT 生成安全令牌 用 Argon2 进行密码哈希和验证 完整的登录流程和错误处理 技术亮点与最佳实践

  1. 安全性设计 项目在安全性方面做了多重保障: 密码存储:使用 Argon2 算法哈希密码,比传统的 bcrypt 更安全 参数校验:使用 DTO 和管道进行严格的参数校验,防止注入攻击 JWT 认证:使用 JWT 进行无状态认证,并设置合理的过期时间 验证码:登录注册时使用验证码防止暴力破解
  2. 模块化与解耦 使用 NestJS 的模块系统组织代码,实现了高内聚低耦合 通过依赖注入实现组件解耦,提高代码可测试性 使用装饰器模式简化代码,提高可读性

3. 异常处理

项目实现了全面的异常处理机制:

CODE
try {   // 业务逻辑 } catch (error) {   console.error('登录处理失败:', error.message);   if (error instanceof UnauthorizedException) {     throw error; // 直接重新抛出认证异常   }   // 其他错误转换为通用错误   throw new UnauthorizedException('登录失败,请检查您的凭据'); }

这种异常处理方式既保留了错误信息的详细度,又避免了向前端泄露敏感信息。

开发中踩过的坑和解决方案

1. JWT 配置与使用

坑:最初设置 JWT 过期时间太短(60秒),导致用户频繁登出。解决方案:

CODE
// 更合理的 JWT 配置 JwtModule.registerAsync({   useFactory: (configService: ConfigService) => ({     secret: configService.get('TOKEN_SECRET'),     signOptions: {        expiresIn: process.env.NODE_ENV === 'production' ? '24h' : '60s'     },   }), }),

2. 验证码存储问题

坑:初期在 Session 中存储验证码时经常丢失,导致验证失败。解决方案:

CODE
await new Promise((resolve) => { req.session.save(() => { console.log('Session 已保存'); resolve(); }); });

3. 代码解耦与优化

坑:初期代码耦合度高,服务层既处理数据库操作,又处理业务逻辑,代码冗长且难以维护。解决方案:

项目优化建议

通过开发和重构,我总结了几点优化建议:

  1. 进一步分离关注点:
  1. 增强安全性:
  1. 提升用户体验:

总结

通过这个项目,我们实现了一个功能完整、安全可靠的用户认证系统。NestJS 的模块化设计和装饰器模式大大提高了开发效率,Prisma 简化了数据库操作,JWT 提供了可靠的身份验证机制。这个项目不仅适合学习 NestJS 和后端认证原理,也可以作为实际项目的认证模块使用。通过合理的架构设计和安全实践,我们构建了一个既安全又可扩展的认证系统。

Loading comments...