所有文章 > API安全 > 使用NestJS和Prisma构建REST API:身份验证

使用NestJS和Prisma构建REST API:身份验证

欢迎阅读本系列的第五篇教程,学习如何使用NestJS、Prisma和PostgreSQL来构建REST API!在本教程中,您将学习如何在NestJS REST API中实现JWT身份验证。

介绍

在本系列的前一章中,您学习了如何在 NestJS REST API 中处理关系数据。创建了一个模型,并在该模型与User模型之间建立了一对多的关系。同时,您还实现了UserArticle模型的CRUD端点。

在本章中,您将学习如何使用名为Passport的包向 API 添加身份验证:

  1. 首先,您将使用名为Passport 的库实现基于 JSON Web Token (JWT) 的身份验证。
  2. 接下来,您将使用bcrypt库对存储在数据库中的密码进行哈希加密,以确保它们的安全性。

在本教程中,您将使用上一章中构建的 API 。

开发环境

要遵循本教程,您需要:

  • 安装了Node.js。
  • 安装了Docker和Docker Compose。如果您使用的是 Linux,请确保您的 Docker 版本为 20.10.0 或更高版本。您可以通过docker version在终端中运行来检查您的 Docker 版本。
  • 可选择安装Prisma VS Code 扩展。 Prisma VS Code 扩展为 Prisma 添加了一些非常好的 IntelliSense 和语法突出显示。
  • 可以选择访问 Unix shell(如 Linux 和 macOS 中的终端/shell)来运行本系列中提供的命令。

如果您没有Unix shell环境(比如您使用的是Windows电脑),您仍然可以按照说明进行操作,但可能需要根据您的电脑系统对shell命令进行相应的调整。

克隆存储库

本教程的起点是本系列第二章的结尾。它包含一个使用 NestJS 构建的基本 REST API。

您可以在GitHub存储库的end-validation分支中找到本教程的起点。首先,请克隆该存储库并切换到相应的分支:

git clone -b end-relational-data git@github.com:prisma/blog-backend-rest-api-nestjs-prisma.git

现在,执行以下操作即可开始:

  1. 导航到克隆的目录:

cd blog-backend-rest-api-nestjs-prisma

  1. 安装依赖项:

npm install

  1. 使用 Docker 启动 PostgreSQL 数据库:

docker-compose up -d

  1. 应用数据库迁移:

npx prisma migrate dev

  1. 启动项目:

npm run start:dev

注意:步骤4还会生成Prisma客户端,并为数据库进行数据播种。

项目结构和文件

您克隆的存储库应具有以下结构:

median
├── node_modules
├── prisma
│ ├── migrations
│ ├── schema.prisma
│ └── seed.ts
├── src
│ ├── app.controller.spec.ts
│ ├── app.controller.ts
│ ├── app.module.ts
│ ├── app.service.ts
│ ├── main.ts
│ ├── articles
│ ├── users
│ └── prisma
├── test
│ ├── app.e2e-spec.ts
│ └── jest-e2e.json
├── README.md
├── .env
├── docker-compose.yml
├── nest-cli.json
├── package-lock.json
├── package.json
├── tsconfig.build.json
└── tsconfig.json

注意:您可能会注意到该文件夹​​还带有一个目录。本教程不涉及测试。

此存储库中值得注意的文件和目录是:

  • src目录包含应用程序的源代码。共有三个模块:
    • app模块位于目录的根目录中src,是应用程序的入口点。它负责启动网络服务器。
    • prisma模块包含 Prisma 客户端,即数据库接口。
    • articles模块定义了路由的端点/articles和伴随的业务逻辑。
    • users模块定义了路由的端点/users和伴随的业务逻辑。
  • prisma文件夹有以下内容:
    • schema.prisma文件定义数据库模式。
    • migrations目录包含数据库迁移历史记录。
    • seed.ts文件包含一个脚本,用于使用虚拟数据为您的开发数据库播种。
  • docker-compose.yml文件定义 PostgreSQL 数据库的 Docker 映像。
  • 这个.env文件包含了PostgreSQL数据库的连接字符串。

在 REST API 中实施身份验证

在本部分中,您将为 REST API 实现大部分身份验证逻辑。到本节结束时,以下端点将受到身份验证保护🔒:

  • GET /users
  • GET /users/:id
  • PATCH /users/:id
  • DELETE /users/:id

Web 上使用的身份验证主要有两种类型:基于会话的身份验证和基于令牌的身份验证。在本教程中,您将利用JSON Web令牌(JWT)来实现基于令牌的身份验证机制。

首先,在您的应用程序中创建一个新模块。运行以下命令生成新模块:

npx nest generate resource

您将看到一些 CLI 提示。回答相应的问题:

  1. 您想为这个资源使用什么名称(复数形式,例如,“users”)?认证
  2. 您使用什么传输层?REST API
  3. 您是否需要生成CRUD(创建、读取、更新、删除)的入口点?不

您现在应该auth在目录中找到一个新模块src/auth

安装和配置

passport是 Node.js 应用程序的流行身份验证库。它具有高度可配置性并支持多种身份验证策略。NestJS是基于Express Web框架构建的,因此它旨在与Express一起使用。NestJS 与passport进行了第一方集成@nestjs/passport,使其可以轻松地在您的 NestJS 应用程序中使用。

首先安装以下软件包:

npm install --save @nestjs/passport passport @nestjs/jwt passport-jwtnpm install --save-dev @types/passport-jwt

现在您已经安装了所需的软件包,您可以passport在应用程序中进行配置。打开src/auth.module.ts文件并添加以下代码:

//src/auth/auth.module.ts
import { Module } from '@nestjs/common';
import { AuthService } from './auth.service';
import { AuthController } from './auth.controller';
import { PassportModule } from '@nestjs/passport';
import { JwtModule } from '@nestjs/jwt';
import { PrismaModule } from 'src/prisma/prisma.module';

export const jwtSecret = 'zjP9h6ZI5LoSKCRj';

@Module({
imports: [
PrismaModule,
PassportModule,
JwtModule.register({
secret: jwtSecret,
signOptions: { expiresIn: '5m' }, // e.g. 30s, 7d, 24h
}),
],
controllers: [AuthController],
providers: [AuthService],
})
export class AuthModule {}

@nestjs/passport模块提供了一个PassportModule,您可以将其导入到您的应用程序中。这个PassportModulepassport库的封装器,为NestJS提供了特定的实用工具。您可以在官方文档中阅读更多关于PassportModule的详细信息。

您还配置了JwtModule,用于生成和验证JWT。JwtModulejsonwebtoken库的封装,它提供了一个secret密钥用于签署JWT,以及一个expiresIn对象来定义JWT的过期时间,当前设置为5分钟。

注意:如果前一个令牌已过期,请记住生成新令牌。

您可以使用jwtSecret代码片段中显示的代码片段或使用 OpenSSL 生成您自己的代码片段。

注意:在真实的应用程序中,您不应该将机密直接存储在代码库中。 NestJS提供了@nestjs/config从环境变量加载秘密的包。

实施端点

POST /login端点将用于对用户进行身份验证。它将接收用户名和密码作为输入,如果凭据验证通过,则返回JWT。首先,您需要创建一个LoginDto类,用于定义请求体的结构。

在目录中创建一个名为login.dto.ts的新文件:

mkdir src/auth/dto
touch src/auth/dto/login.dto.ts

现在用emailLoginDto字段定义类:

//src/auth/dto/login.dto.ts
import { ApiProperty } from '@nestjs/swagger';
import { IsEmail, IsNotEmpty, IsString, MinLength } from 'class-validator';

export class LoginDto {
@IsEmail()
@IsNotEmpty()
@ApiProperty()
email: string;

@IsString()
@IsNotEmpty()
@MinLength(6)
@ApiProperty()
password: string;
}

您还需要定义一个新的AuthEntity来描述JWT有效负载的结构。请在auth.entity.ts文件中进行定义。

mkdir src/auth/entity
touch src/auth/entity/auth.entity.ts

现在在此文件中定义:

//src/auth/entity/auth.entity.ts
import { ApiProperty } from '@nestjs/swagger';

export class AuthEntity {
@ApiProperty()
accessToken: string;
}

AuthEntity只有一个名为accessToken的字符串字段,该字段包含JWT。

现在在里面创建一个新login方法:

//src/auth/auth.service.ts
import {
Injectable,
NotFoundException,
UnauthorizedException,
} from '@nestjs/common';
import { PrismaService } from './../prisma/prisma.service';
import { JwtService } from '@nestjs/jwt';
import { AuthEntity } from './entity/auth.entity';

@Injectable()
export class AuthService {
constructor(private prisma: PrismaService, private jwtService: JwtService) {}

async login(email: string, password: string): Promise<AuthEntity> {
// Step 1: Fetch a user with the given email
const user = await this.prisma.user.findUnique({ where: { email: email } });

// If no user is found, throw an error
if (!user) {
throw new NotFoundException(`No user found for email: ${email}`);
}

// Step 2: Check if the password is correct
const isPasswordValid = user.password === password;

// If password does not match, throw an error
if (!isPasswordValid) {
throw new UnauthorizedException('Invalid password');
}

// Step 3: Generate a JWT containing the user's ID and return it
return {
accessToken: this.jwtService.sign({ userId: user.id }),
};
}
}

login方法首先获取具有给定电子邮件的用户。如果没有找到用户,系统会抛出一个NotFoundException。如果找到了用户,系统会进一步检查密码是否正确。如果密码不正确,则会抛出一个UnauthorizedException。如果密码正确,它会生成一个包含用户 ID 的 JWT 并将其返回。

现在,在AuthController中创建一个处理POST /auth/login请求的方法:

//src/auth/auth.controller.ts
+import { Body, Controller, Post } from '@nestjs/common';import { AuthService } from './auth.service';+import { ApiOkResponse, ApiTags } from '@nestjs/swagger';+import { AuthEntity } from './entity/auth.entity';+import { LoginDto } from './dto/login.dto';
@Controller('auth')+@ApiTags('auth')export class AuthController { constructor(private readonly authService: AuthService) {}
+ @Post('login')+ @ApiOkResponse({ type: AuthEntity })+ login(@Body() { email, password }: LoginDto) {+ return this.authService.login(email, password);+ }}

现在,您的API中应该有一个新的端点POST /auth/login

转到该http://localhost:3000/api页面并尝试POST /auth/login端点。提供您在种子脚本中创建的用户的凭据。

您可以使用以下请求正文:

{  "email": "sabin@adams.com",  "password": "password-sabin"}

执行请求后,您应该在响应中获得 JWT。

POST /auth/login 端点

在下一部分中,您将使用此令牌对用户进行身份验证。

实施JWT认证策略

在 Passport 中,策略负责对请求进行身份验证,这是通过实现身份验证机制来完成的。在本部分中,您将实现用于对用户进行身份验证的 JWT 身份验证策略。

您不会直接使用passport这个包,而是与@nestjs/passport这个包装器包进行交互,后者会在幕后调用passport包。要使用配置策略@nestjs/passport,您需要创建一个扩展该类的类PassportStrategy。在这个课程中你需要做两件主要的事情:

  1. 您将把 JWT 策略特定的选项和配置传递给super()构造函数中的方法。
  2. 一个validate()回调方法,它将与您的数据库进行交互,根据JWT负载中的信息来获取用户。如果成功找到用户,该validate()方法应当返回该用户对象。

首先在jwt.strategy.ts目录中创建一个名为src/auth/strategy的新文件:

touch src/auth/jwt.strategy.ts

现在实现JwtStrategy类:

//src/auth/jwt.strategy.ts
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';
import { jwtSecret } from './auth.module';
import { UsersService } from 'src/users/users.service';

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy, 'jwt') {
constructor(private usersService: UsersService) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: jwtSecret,
});
}

async validate(payload: { userId: number }) {
const user = await this.usersService.findOne(payload.userId);

if (!user) {
throw new UnauthorizedException();
}

return user;
}
}

您已经创建了一个名为JwtStrategy的类,该类扩展了PassportStrategy。该类PassportStrategy采用两个参数:策略实现和策略名称。在这里,您正在使用库中的预定义策略passport-jwt

您在super()构造函数中向该方法传递了一些选项。其中,jwtFromRequest选项需要一个函数,该函数能够从请求中提取JWT。在这种情况下,您将使用在API请求的Authorization头部中提供Bearer令牌的标准方法。secretOrKey选项则告诉策略应该使用什么密钥来验证JWT。还有更多其他的选项可供配置,您可以在passport-jwt的官方存储库中查阅详细信息。

对于passport-jwt,Passport 首先验证 JWT 的签名并解码 JSON。然后将解码后的 JSON 传递给该validate()方法。根据 JWT 签名的工作方式,您可以保证收到之前由您的应用程序签名和颁发的有效令牌。该validate()方法预计返回一个用户对象。如果未找到用户,该validate()方法将引发错误。

注意:Passport可能会让人感到有些复杂。但将其本身视为一个迷你框架是很有帮助的,因为它将身份验证过程抽象为了几个步骤,这些步骤可以通过策略和配置选项进行自定义。

JwtStrategy中添加新的AuthModule作为提供程序:

//src/auth/auth.module.ts
import { Module } from '@nestjs/common';
import { AuthService } from './auth.service';
import { AuthController } from './auth.controller';
import { PassportModule } from '@nestjs/passport';
import { JwtModule } from '@nestjs/jwt';
import { PrismaModule } from 'src/prisma/prisma.module';
import { UsersModule } from 'src/users/users.module';
import { JwtStrategy } from './jwt.strategy';

export const jwtSecret = 'zjP9h6ZI5LoSKCRj';

@Module({
imports: [
PrismaModule,
PassportModule,
JwtModule.register({
secret: jwtSecret,
signOptions: { expiresIn: '5m' }, // e.g. 7d, 24h
}),
UsersModule,
],
controllers: [AuthController],
providers: [AuthService, JwtStrategy],
})
export class AuthModule {}

现在,JwtStrategy可以被其他模块所使用了。此外,您还在UsersModule中添加了相应的imports,因为UsersService类中正在使用JwtStrategy

要使UsersServiceJwtStrategy类中可访问,您还需要将其添加到exportsUsersModule中:

// src/users/users.module.ts
import { Module } from '@nestjs/common';
import { UsersService } from './users.service';
import { UsersController } from './users.controller';
import { PrismaModule } from 'src/prisma/prisma.module';

@Module({
controllers: [UsersController],
providers: [UsersService],
imports: [PrismaModule],
exports: [UsersService],
})
export class UsersModule {}

实施 JWT 身份验证防护

Guards是一种 NestJS 构造,用于确定是否允许请求继续进行。在本部分中,您将实现一个自定义JwtAuthGuard,用于保护需要身份验证的路由。

jwt-auth.guard.ts在目录中创建一个名为的新文件src/auth

touch src/auth/jwt-auth.guard.ts

现在实现JwtAuthGuard类:

//src/auth/jwt-auth.guard.tsimport { Injectable } from '@nestjs/common';import { AuthGuard } from '@nestjs/passport';
@Injectable()export class JwtAuthGuard extends AuthGuard('jwt') {}

该类AuthGuard需要策略的名称。在本例中,您将使用JwtStrategy在上一节中实现的名为 的jwt

您现在可以使用这个守卫(Guard)作为装饰器来保护您的端点。请将JwtAuthGuard添加到UsersController的路由中:

// src/users/users.controller.ts
import {
Controller,
Get,
Post,
Body,
Patch,
Param,
Delete,
ParseIntPipe,
UseGuards,
} from '@nestjs/common';
import { UsersService } from './users.service';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';
import { ApiCreatedResponse, ApiOkResponse, ApiTags } from '@nestjs/swagger';
import { UserEntity } from './entities/user.entity';
import { JwtAuthGuard } from 'src/auth/jwt-auth.guard';

@Controller('users')
@ApiTags('users')
export class UsersController {
constructor(private readonly usersService: UsersService) {}

@Post()
@ApiCreatedResponse({ type: UserEntity })
async create(@Body() createUserDto: CreateUserDto) {
return new UserEntity(await this.usersService.create(createUserDto));
}

@Get()
@UseGuards(JwtAuthGuard)
@ApiOkResponse({ type: UserEntity, isArray: true })
async findAll() {
const users = await this.usersService.findAll();
return users.map((user) => new UserEntity(user));
}

@Get(':id')
@UseGuards(JwtAuthGuard)
@ApiOkResponse({ type: UserEntity })
async findOne(@Param('id', ParseIntPipe) id: number) {
return new UserEntity(await this.usersService.findOne(id));
}

@Patch(':id')
@UseGuards(JwtAuthGuard)
@ApiCreatedResponse({ type: UserEntity })
async update(
@Param('id', ParseIntPipe) id: number,
@Body() updateUserDto: UpdateUserDto,
) {
return new UserEntity(await this.usersService.update(id, updateUserDto));
}

@Delete(':id')
@UseGuards(JwtAuthGuard)
@ApiOkResponse({ type: UserEntity })
async remove(@Param('id', ParseIntPipe) id: number) {
return new UserEntity(await this.usersService.remove(id));
}
}

如果您尝试在未经身份验证的情况下查询任何这些端点,它将不再起作用。

`GET /users 端点给出 401 响应

在 Swagger 中集成身份验证

目前,在Swagger上还没有任何迹象表明这些端点受到了身份验证的保护。您可以向控制器添加@ApiBearerAuth()装饰器,以指示这些端点需要进行身份验证:

// src/users/users.controller.ts

import {
Controller,
Get,
Post,
Body,
Patch,
Param,
Delete,
ParseIntPipe,
UseGuards,
} from '@nestjs/common';
import { UsersService } from './users.service';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';
import { ApiBearerAuth, ApiCreatedResponse, ApiOkResponse, ApiTags } from '@nestjs/swagger';
import { UserEntity } from './entities/user.entity';
import { JwtAuthGuard } from 'src/auth/jwt-auth.guard';

@Controller('users')
@ApiTags('users')
export class UsersController {
constructor(private readonly usersService: UsersService) {}

@Post()
@ApiCreatedResponse({ type: UserEntity })
async create(@Body() createUserDto: CreateUserDto) {
return new UserEntity(await this.usersService.create(createUserDto));
}

@Get()
@UseGuards(JwtAuthGuard)
@ApiBearerAuth()
@ApiOkResponse({ type: UserEntity, isArray: true })
async findAll() {
const users = await this.usersService.findAll();
return users.map((user) => new UserEntity(user));
}

@Get(':id')
@UseGuards(JwtAuthGuard)
@ApiBearerAuth()
@ApiOkResponse({ type: UserEntity })
async findOne(@Param('id', ParseIntPipe) id: number) {
return new UserEntity(await this.usersService.findOne(id));
}

@Patch(':id')
@UseGuards(JwtAuthGuard)
@ApiBearerAuth()
@ApiCreatedResponse({ type: UserEntity })
async update(
@Param('id', ParseIntPipe) id: number,
@Body() updateUserDto: UpdateUserDto,
) {
return new UserEntity(await this.usersService.update(id, updateUserDto));
}

@Delete(':id')
@UseGuards(JwtAuthGuard)
@ApiBearerAuth()
@ApiOkResponse({ type: UserEntity })
async remove(@Param('id', ParseIntPipe) id: number) {
return new UserEntity(await this.usersService.remove(id));
}
}

现在,受身份验证保护的端点在 Swagger 中应该有一个锁图标 🔓

Swagger 中的身份验证受保护端点

目前无法直接在 Swagger 中“验证”自己,因此您可以测试这些端点。为此,您可以在main.ts中的SwaggerModule设置里添加.addBearerAuth()方法调用:

// src/main.ts

import { NestFactory, Reflector } from '@nestjs/core';
import { AppModule } from './app.module';
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
import { ClassSerializerInterceptor, ValidationPipe } from '@nestjs/common';

async function bootstrap() {
const app = await NestFactory.create(AppModule);

app.useGlobalPipes(new ValidationPipe({ whitelist: true }));
app.useGlobalInterceptors(new ClassSerializerInterceptor(app.get(Reflector)));

const config = new DocumentBuilder()
.setTitle('Median')
.setDescription('The Median API description')
.setVersion('0.1')
.addBearerAuth()
.build();
const document = SwaggerModule.createDocument(app, config);
SwaggerModule.setup('api', app, document);

await app.listen(3000);
}
bootstrap();

现在,您可以通过单击 Swagger 中的“授权”按钮来添加令牌。 Swagger 会将令牌添加到您的请求中,以便您可以查询受保护的端点。

注意:您可以通过向/auth/login端点发送一个包含有效emailpassword的POST请求来生成令牌。

自己尝试一下。

Swagger 中的身份验证工作流程

哈希密码

目前,该User.password字段以纯文本形式存储。这是一个重大的安全风险,因为如果数据库遭到泄露,那么所有密码也将随之暴露。为了解决这个问题,我们可以在将密码存储到数据库之前先对其进行哈希处理。

您可以使用bcrypt加密库来散列密码。使用npm安装它:

npm install bcrypt
npm install --save-dev @types/bcrypt

首先,您将更新中的createupdate方法以在将密码存储到数据库之前对密码进行哈希处理:

// src/users/users.service.ts
import { Injectable } from '@nestjs/common';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';
import { PrismaService } from 'src/prisma/prisma.service';
import * as bcrypt from 'bcrypt';

export const roundsOfHashing = 10;

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

async create(createUserDto: CreateUserDto) {
const hashedPassword = await bcrypt.hash(
createUserDto.password,
roundsOfHashing,
);

createUserDto.password = hashedPassword;

return this.prisma.user.create({
data: createUserDto,
});
}

findAll() {
return this.prisma.user.findMany();
}

findOne(id: number) {
return this.prisma.user.findUnique({ where: { id } });
}

async update(id: number, updateUserDto: UpdateUserDto) {
if (updateUserDto.password) {
updateUserDto.password = await bcrypt.hash(
updateUserDto.password,
roundsOfHashing,
);
}

return this.prisma.user.update({
where: { id },
data: updateUserDto,
});
}

remove(id: number) {
return this.prisma.user.delete({ where: { id } });
}
}

bcrypt.hash函数接受两个参数:哈希函数的输入字符串和哈希轮数(也称为成本因子)。增加哈希值的计算时间可以通过提高哈希轮数来实现,但这也需要在安全性和性能之间做出权衡。具体来说,哈希轮数越多,计算所需的时间就越长,从而有助于增强对暴力攻击的防御能力。然而,更多轮的散列也意味着用户登录时需要更多的时间来计算散列。这个堆栈溢出的答案对这个主题进行了深入的讨论。

bcrypt还会自动使用另一种称为salting 的技术来增加暴力破解哈希的难度。salting是一种在散列之前将随机字符串添加到输入字符串的技术。这样,由于每个密码都使用了不同的盐值,攻击者就无法利用预先计算好的哈希表来破解密码了。

您还需要更新数据库种子脚本以在将密码插入数据库之前对密码进行哈希处理:

// prisma/seed.ts
import { PrismaClient } from '@prisma/client';
import * as bcrypt from 'bcrypt';

// initialize the Prisma Client
const prisma = new PrismaClient();

const roundsOfHashing = 10;

async function main() {
// create two dummy users
const passwordSabin = await bcrypt.hash('password-sabin', roundsOfHashing);
const passwordAlex = await bcrypt.hash('password-alex', roundsOfHashing);

const user1 = await prisma.user.upsert({
where: { email: 'sabin@adams.com' },
update: {
password: passwordSabin,
},
create: {
email: 'sabin@adams.com',
name: 'Sabin Adams',
password: passwordSabin,
},
});

const user2 = await prisma.user.upsert({
where: { email: 'alex@ruheni.com' },
update: {
password: passwordAlex,
},
create: {
email: 'alex@ruheni.com',
name: 'Alex Ruheni',
password: passwordAlex,
},
});

// create three dummy posts
// ...
}

// execute the main function
// ...

运行种子脚本npx prisma db seed后,您应该会注意到数据库中存储的密码都已经经过了哈希处理。

...
Running seed command `ts-node prisma/seed.ts` ...
{
user1: {
id: 1,
name: 'Sabin Adams',
email: 'sabin@adams.com',
password: '$2b$10$XKQvtyb2Y.jciqhecnO4QONdVVcaghDgLosDPeI0e90POYSPd1Dlu',
createdAt: 2023-03-20T22:05:56.758Z,
updatedAt: 2023-04-02T22:58:05.792Z
},
user2: {
id: 2,
name: 'Alex Ruheni',
email: 'alex@ruheni.com',
password: '$2b$10$0tEfezrEd1a2g51lJBX6t.Tn.RLppKTv14mucUSCv40zs5qQyBaw6',
createdAt: 2023-03-20T22:05:56.772Z,
updatedAt: 2023-04-02T22:58:05.808Z
},
...

password字段的值对您来说会有所不同,因为每次都会使用不同的盐值进行哈希处理。重要的是,现在这个值已经是一个哈希字符串了。

现在,如果您尝试使用正确的密码,您将面临HTTP 401错误。这是因为该login方法尝试将用户请求中的明文密码与数据库中的哈希密码进行比较。更新login方法以使用哈希密码:

//src/auth/auth.service.ts
import { AuthEntity } from './entity/auth.entity';
import { PrismaService } from './../prisma/prisma.service';
import {
Injectable,
NotFoundException,
UnauthorizedException,
} from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import * as bcrypt from 'bcrypt';

@Injectable()
export class AuthService {
constructor(private prisma: PrismaService, private jwtService: JwtService) {}

async login(email: string, password: string): Promise<AuthEntity> {
const user = await this.prisma.user.findUnique({ where: { email } });

if (!user) {
throw new NotFoundException(`No user found for email: ${email}`);
}

const isPasswordValid = await bcrypt.compare(password, user.password);

if (!isPasswordValid) {
throw new UnauthorizedException('Invalid password');
}

return {
accessToken: this.jwtService.sign({ userId: user.id }),
};
}
}

您现在可以使用正确的密码登录并在响应中获取 JWT。

总结和最后评论

在本章中,您学习了如何在 NestJS REST API 中实现 JWT 身份验证。您还了解了对密码进行加盐处理以及将身份验证与 Swagger 集成。

您可以在GitHub存储库的end-authentication分支上找到本教程的完成代码。如果您发现问题,请随时在存储库中提出问题或提交 PR。

原文链接:https://www.prisma.io/blog/nestjs-prisma-authentication-7D056s1s0k3l

#你可能也喜欢这些API文章!