继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

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

aluckdog
关注TA
已关注
手记 473
粉丝 68
获赞 394

在这节教程里,我们来使用 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" 和学术的含义。

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

打开App,阅读手记
0人推荐
发表评论
随时随地看视频慕课网APP