为了账号安全,请及时绑定邮箱和手机立即绑定

用NestJS、JWT认证和Prisma搭建安全的后端系统

标签:
Node.js 安全

在这节教程里,我们来使用 NestJS、Prisma 和基于 JWT 的认证来创建一个安全的后端程序。我们的应用将包括管理书籍的 CRUD 操作,并且端点会通过 JWT 认证来保护。

先决条件

在开始之前,请确保您的机器上已安装了以下软件。

  • Node.jsnpm(建议安装长期稳定版)
  • 全局安装 Nest CLI 时,请使用命令 npm install -g @nestjs/cli
  • PostgreSQL(或其他支持 Prisma 的数据库)已经运行并且可以访问
首先,让我们新建一个 NestJS 项目

首先,使用 Nest CLI 来创建一个NestJS项目:

    开新书店
    逛书店
第二步:安装依赖项:

接下来,按照以下步骤安装JWT身份验证和Prisma所需的必要依赖项。

npm install @nestjs/jwt @nestjs/passport passport passport-jwt @prisma/client prisma
第 3 步:初始化 Prisma

如果你使用的是 Postgresql 的 Docker 镜像,就在 docker-compose.yml 文件里加上下面这些内容。

    version: '3.8'  
    services:  
      postgres:  
        container_name: postgres_container  
        image: postgres:13  
        ports:  
          - 5434:5432  
        environment:  
          设置环境变量如下:POSTGRES_USER: postgres, POSTGRES_PASSWORD: 123, POSTGRES_DB: book-store  
          # 数据库名称: book-store  
        volumes:  
          - postgres_data:/var/lib/postgresql/data

更新你的 .env 文件,包含你的数据库连接字符串,例如:DB_CONNECTION_STRING

    DATABASE_URL="postgresql://postgres:123@localhost:5434/book-store?schema=public" # 连接到本地PostgreSQL数据库的URL

在你的项目中初始化 Prisma 并配置数据库连接:

    npx prisma init

注:此命令用于初始化Prisma项目。

步骤 4:配置 Prisma 模式

prisma/schema.prisma 文件中添加 UserBook 模型:

    数据源 db {  
      提供器 = "postgresql"  
      url      = env("DATABASE_URL")  
    }  

    生成器 client {  
      提供器 = "prisma-client-js"  
    }  

    模型 User {  
      id       Int     @标识符 @default(autoincrement())  
      createdAt DateTime @default(now())  
      updatedAt DateTime @updatedAt  
      email    String  @unique  

      firstName String?  
      lastName  String?  

      password String  
    }  

    模型 Book {  
      id       Int    @标识符 @default(autoincrement())  
      createdAt DateTime @default(now())  
      updatedAt DateTime @updatedAt  
      title       String  
      description String?  
      link        String  
      用户ID   Int  
    }

运行 Prisma 迁移命令以将模式应用到数据库中:请注意,Prisma 是一个特定的工具或框架名称。

运行数据库迁移的命令:

    npx prisma migrate dev --name init

生成 Prisma 客户端代码:

你可以运行以下命令来生成代码:npx prisma generate.

第 5 步:设置认证:

生成 Auth 模块、控制器和相关服务:

    nest 创建 auth 模块  
    nest 创建 auth 控制器  
    nest 创建 auth 服务

设置认证模块:

    import { Module } from '@nestjs/common'; // 提供了NestJS的基本功能
    import { JwtModule } from '@nestjs/jwt'; // 用于处理JWT的认证
    import { PassportModule } from '@nestjs/passport'; // 用于集成Passport中间件
    import { AuthService } from './auth.service'; // 提供了认证服务
    import { AuthController } from './auth.controller'; // 控制层,处理认证相关的HTTP请求
    import { JwtStrategy } from './jwt.strategy'; // JWT认证策略
    import { PrismaService } from '../prisma.service'; // 数据库操作服务

    /**

* @Module: 定义了一个模块,该模块包含了导入的模块、提供者和控制器。
     */
    @Module({  
      imports: [  
        PassportModule, // 用于集成Passport中间件
        JwtModule.register({  
          secret: process.env.JWT_SECRET || 'secretKey', // 密钥,如果环境变量中没有设置JWT_SECRET,则使用'秘密密钥'
          signOptions: { expiresIn: '60m' }, // 签发选项,设置令牌的有效期为60分钟
        }),  
      ],  
      providers: [AuthService, JwtStrategy, PrismaService], // 提供者列表,包括认证服务、JWT策略和数据库操作服务
      controllers: [AuthController], // 控制器列表,处理认证相关的HTTP请求
    })  
    export class AuthModule {} // 定义了一个认证模块

配置 auth.service.ts 文件

实现一个支持注册和登录功能的AuthService:

    import { Injectable } from '@nestjs/common';  
    import { JwtService } from '@nestjs/jwt';  
    import { PrismaService } from '../prisma.service';  
    import * as bcrypt from 'bcrypt';  

    // 注册为可注入的类
    @Injectable()  
    export class AuthService {  
      // 构造函数
      constructor(  
        private jwtService: JwtService,  
        private prisma: PrismaService  
      ) {}  

      // 验证用户并验证密码
      async validateUser(email: string, pass: string): Promise<any> {  
        const user = await this.prisma.user.findUnique({ where: { email } });  
        if (user && await bcrypt.compare(pass, user.password)) {  
          const { password, ...result } = user;  
          return result;  
        }  
        return null;  
      }  

      // 生成并返回登录令牌
      async login(user: any) {  
        const payload = { email: user.email, sub: user.id };  
        return {  
          access_token: this.jwtService.sign(payload),  
        };  
      }  

      // 注册新用户并返回用户信息(不包括密码)
      async register(email: string, pass: string) {  
        const salt = await bcrypt.genSalt();  
        const hashedPassword = await bcrypt.hash(pass, salt);  

        const user = await this.prisma.user.create({  
          data: {  
            email,  
            password: hashedPassword,  
          },  
        });  

        const { password, ...result } = user;  
        return result;  
      }  
    }

设置 auth.controller.ts

在 AuthController 里创建登录和注册的端点:

    引入 { Controller, Post, Body } from '@nestjs/common';  
    引入 { AuthService } from './auth.service';  

    @Controller('auth')  
    出口类 AuthController {  
      constructor(私有 authService: AuthService) {}  

      @Post('登录')  
      异步登录(@Body() req) {  
        返回 this.authService.login(req);  
      }  

      @Post('注册')  
      异步注册(@Body() req) {  
        返回 this.authService.register(req.email, req.password);  
      }  
    }

设置一下jwt.strategy.ts

    import { Injectable } from '@nestjs/common';  
    import { PassportStrategy } from '@nestjs/passport';  
    import { ExtractJwt, Strategy } from 'passport-jwt';  

    /**

* JwtStrategy 类继承了 PassportStrategy,并使用了 Strategy 策略。

* 这个类的主要作用是验证 JWT 令牌并从令牌中提取用户信息。
     */
    @Injectable()  
    export class JwtStrategy extends PassportStrategy(Strategy) {  
      /**

* 构造函数中,设置了从请求头中提取 JWT 令牌的方法、是否忽略过期时间以及密钥。
       */
      constructor() {  
        super({  
          jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), // 从请求头中提取 JWT 令牌
          ignoreExpiration: false, // 不忽略过期时间
          secretOrKey: process.env.JWT_SECRET || 'secretKey', // 密钥或密钥环境变量
        });  
      }  

      /**

* 验证方法,从 JWT 令牌中提取用户 ID 和电子邮件地址。
       */
      async validate(payload: any) {  
        return { userId: payload.sub, email: payload.email };  
      }  
    }

创建一个认证闸门(jwt-auth.guard.ts),

导入 { Injectable } from '@nestjs/common';  
导入 { AuthGuard } from '@nestjs/passport';  

@可注入  
导出 class JwtAuthGuard extends AuthGuard('jwt') {}
第六步:设置 Prisma 服务端 (Prisma,一个数据库访问层工具)

创建一个名为 prisma.service 的 Prisma 服务来处理与数据库的交互:

    从 '@nestjs/common' 导入 { Injectable, OnModuleInit, OnModuleDestroy };  
    从 '@prisma/client' 导入 { PrismaClient };  

    @可注入()  
    导出类 PrismaService 继承自 PrismaClient 并实现 OnModuleInit 和 OnModuleDestroy {  
      异步 onModuleInit() {  
        await this.$connect(); // 等待连接数据库  
      }  

      异步 onModuleDestroy() {  
        await this.$disconnect(); // 等待断开数据库连接  
      }  
    }

步骤 7:创建书本模块

创建 Books 模块和对应的控制器、服务:

生成一个名为 'books' 的模块:
nest generate module books  
这将生成一个名为 'books' 的模块。

生成一个名为 'books' 的控制器:
nest generate controller books  
这将生成一个名为 'books' 的控制器。

生成一个名为 'books' 的服务:
nest generate service books  
这将生成一个名为 'books' 的服务。

设置 Books 模块(在 books.module.ts 文件中):

    import { Module } from '@nestjs/common';  
    import { BooksService } from './books.service';  
    import { BooksController } from './books.controller';  
    import { PrismaService } from '../prisma.service';  

    @Module({  
      providers: [BooksService, PrismaService],  
      controllers: [BooksController]  
    })  
    export class BooksModule {}

实现 BooksService(books.service.ts)功能:

     import { Injectable } from '@nestjs/common';  
    import { PrismaService } from '../prisma.service';  
    import { Book } from '@prisma/client';  

    @Injectable()  
    export class BooksService {  
      constructor(private prisma: PrismaService) {}  

      /**

* 创建一个新的书籍条目

* @param data 书籍数据,不包含id

* @returns Promise<Book>
       */
      async create(data: Omit<Book, 'id'>): Promise<Book> {  
        return this.prisma.book.create({ data });  
      }  

      /**

* 获取用户的所有书籍条目

* @param userId 用户ID

* @returns Promise<Book[]>
       */
      async findAll(userId: number): Promise<Book[]> {  
        return this.prisma.book.findMany({ where: { userId } });  
      }  

      /**

* 获取单个书籍条目

* @param id 书籍ID

* @param userId 用户ID

* @returns Promise<Book>
       */
      async findOne(id: number, userId: number): Promise<Book> {  
        return this.prisma.book.findFirst({ where: { id, userId } });  
      }  

      /**

* 更新书籍条目

* @param id 书籍ID

* @param data 更新数据

* @param userId 用户ID

* @returns Promise<Book>
       */
      async update(id: number, data: Partial<Book>, userId: number): Promise<Book> {  
        return this.prisma.book.updateMany({  
          where: { id, userId },  
          data,  
        }).then((result) => result.count ? this.prisma.book.findUnique({ where: { id } }) : null);  
      }  

      /**

* 删除书籍条目

* @param id 书籍ID

* @param userId 用户ID

* @returns Promise<Book>
       */
      async remove(id: number, userId: number): Promise<Book> {  
        return this.prisma.book.deleteMany({  
          where: { id, userId },  
        }).then((result) => result.count ? this.prisma.book.findUnique({ where: { id } }) : null);  
      }  
    }

通过 JWT 认证保护 BooksController 的安全。

    import { Controller, Get, Post, Body, Patch, Param, Delete, UseGuards, Request } from '@nestjs/common';  
    import { BooksService } from './books.service';  
    import { JwtAuthGuard } from '../auth/jwt-auth.guard';  

    @Controller('books')  
    @UseGuards(JwtAuthGuard)  
    export class BooksController {  
      constructor(private readonly booksService: BooksService) {}  

      @Post()  
      create(@Body() createBookDto, @Request() req) {  
        return this.booksService.create({ ...createBookDto, userId: req.user.userId });  
      }  

      @Get()  
      findAll(@Request() req) {  
        return this.booksService.findAll(req.user.userId);  
      }  

      @Get(':id')  
      findOne(@Param('id') id: string, @Request() req) {  
        return this.booksService.findOne(+id, req.user.userId);  
      }  

      @Patch(':id')  
      update(@Param('id') id: string, @Body() updateBookDto, @Request() req) {  
        return this.booksService.update(+id, updateBookDto, req.user.userId);  
      }  

      @Delete(':id')  
      remove(@Param('id') id: string, @Request() req) {  
        return this.booksService.remove(+id, req.user.userId);  
      }  
    }
第八步:把所有的东西整合起来

确认所有模块都正确导入到主应用程序模块中。

// 导入 NestJS 的 Module 类
import { Module } from '@nestjs/common';  
// 导入自定义的认证模块
import { AuthModule } from './auth/auth.module';  
// 导入自定义的书籍模块
import { BooksModule } from './books/books.module';  

// 创建一个应用模块,该模块依赖于认证模块和书籍模块
@Module({  
  imports: [AuthModule, BooksModule],  
})  
export class AppModule {}
运行应用

使用以下命令来运行您的应用程序:

运行开发环境: npm run start:dev (这将启动开发环境)

所以,最后我们得出结论是...

在这篇教程中,我们创建了一个使用 Prisma 进行数据库交互和使用 JWT 保护 API 端点的安全 NestJS 应用程序。我们涵盖了设置 Prisma 架构文件、创建用于身份验证和书籍的模块化实现以及保护端点的安全。你现在拥有了一个使用 JWT 进行认证,并具备书籍 CRUD 操作的安全 NestJS 后端。

参考文献
文档 | NestJS - 一个渐进的 Node.js 框架Nest 是一个用于构建高效和可扩展的 Node.js 服务器端应用的框架。它使用了渐进式 JavaScript…docs.nestjs.com
Prisma 文档开始使用 Prisma 的官方文档,请点击这里了解更多关于 Prisma 的内容…www.prisma.io
JWT.IO - JSON Web Tokens 介绍什么是 JSON Web Tokens,它们的工作原理,何时该用以及为什么使用它们。jwt.io

GitHub : https://github.com/tharindu1998/book-store

Stackademic 🎓

解释:Stackademic 结合了 "Stack" 和学术的含义。

读到最后,感谢您的陪伴。在您离开时,

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消