认证是任何应用程序的最重要方面之一。它通过在允许用户访问应用程序的不同部分之前对其进行验证,从而提高了应用程序的安全性。认证也使公司能够跟踪有多少人在使用他们的产品。
正确配置认证是非常重要的。事实上,开放网络应用安全项目(OWASP)在其十大网络应用安全风险列表中指出了识别和认证失败。
本教程将展示在NestJS中实现JWT用户认证的逐步过程。
前提条件
本教程是一个实战演示。要跟上进度,请确保你已经安装了以下设备:
什么是NestJS?
NestJS是一个用于Node.js的服务器端应用程序框架,允许你创建可扩展和高效的应用程序。它是用TypeScript编写的,并与Express.js一起构建,Express.js是一个轻量级的框架,其本身非常棒,但缺乏结构。
Nest支持面向对象编程、函数式编程和功能性反应式编程。如果你想在你的应用程序的后端有很多结构,这个框架工作是一个很好的选择。
Nest的语法和结构与Angular(一个前端框架)相似。它也采用了TypeScript、服务和依赖注入,就像Angular一样。Nest使用模块和控制器,并允许你使用命令行界面来创建文件的控制器。
开始使用
要设置该项目,你首先需要用以下命令在全球范围内安装Nest CLI:
npm i -g @nestjs/cli
安装完成后,创建一个新项目,像这样:
nest new auth-with-nest
接下来,你会被提示选择一个软件包管理器来安装依赖性。对于这个演示,我们将使用Yarn。

选择yarn ,然后按回车键。现在,等待Yarn安装运行应用程序所需的所有依赖项。
设置MongoDB数据库
为了设置和连接你的数据库,用以下命令安装Mongoose包、bcrypt和NestJS包装器:
npm install --save @nestjs/mongoose @types/bcrypt mongoose bcrypt
现在,更新app.module.ts 文件并设置Mongoose,像这样:
import { MongooseModule } from '@nestjs/mongoose';
@Module({
imports: [MongooseModule.forRoot('mongodb://localhost/authentication')],
})
在上面的片段中,我们将MongooseModule 导入到根AppModule 。
创建用户模块
为了保持你的代码整洁和良好的组织,通过运行以下命令,为NestJS CLI用户专门创建一个模块:
nest g module users
上面的代码创建了一个用户文件夹,里面有一个users.module.ts 文件和一个app.module.ts 更新文件。
创建用户模式
要创建一个用户模式,在src/users 文件夹中创建一个users.model.ts 文件并添加以下代码:
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { Document } from 'mongoose';
export type UserDocument = User & Document;
@Schema()
export class User {
@Prop()
username: string;
@Prop()
password: string;
}
export const UserSchema = SchemaFactory.createForClass(User);
在这里,我们已经用@Schema() 装饰器和@Prop() 装饰器定义了我们的User 模式的形状。
Mongoose将把该模式映射到MongoDB集合中。该模式定义了该集合的文档形状。
现在,用以下代码替换user/user.module.ts 文件中的代码,并使userSchema 在导入中可用:
import { Module } from '@nestjs/common';
import { UsersService } from './user.service';
import { UsersController } from './user.controller';
import { MongooseModule } from "@nestjs/mongoose"
import { UserSchema } from "./user.model"
@Module({
imports: [MongooseModule.forFeature([{ name: "user", schema: UserSchema }])],
providers: [UsersService],
controllers: [UsersController]
})
export class UserModule {}
创建用户服务
在创建了用户模式之后,运行下面的命令来创建一个用户服务:
nest g service users
这段代码创建了一个users.service.ts 文件并更新了app.module.ts 文件。
注意。 你可以选择手动创建你的文件和文件夹,但NestJS CLI会自动更新必要的文件夹,使你的生活更轻松。
现在,在users.service.ts 文件中添加以下代码:
import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Model } from 'mongoose';
import { User, UserDocument } from './users.model';
@Injectable()
export class UsersService {
constructor(@InjectModel('user') private readonly userModel: Model<UserDocument>) { }
async createUser(username: string, password: string): Promise<User> {
return this.userModel.create({
username,
password,
});
}
async getUser(query: object ): Promise<User> {
return this.userModel.findOne(query);
}
}
在这里,我们使用@InjectModel() 装饰器,将userModel 注入到UsersService 。
创建用户控制器
现在让我们创建一个用户控制器来定义API路由:
nest g service users
将代码添加到users.controller.ts 文件中:
import { Body, Controller, Post, Get, Param } from '@nestjs/common';
import { UsersService } from './users.service';
import { User } from './users.model';
import * as bcrypt from 'bcrypt';
@Controller('auth')
export class UsersController {
constructor(private readonly usersService: UsersService) { }
@Post('/signup')
async createUser(
@Body('password') password: string,
@Body('username') username: string,
): Promise<User> {
const saltOrRounds = 10;
const hashedPassword = await bcrypt.hash(password, saltOrRounds);
const result = await this.usersService.createUser(
username,
hashedPassword,
);
return result;
}
}
在这里,我们定义了两个API路由,并消耗了我们创建的服务。我们使用bcrypt ,对用户密码进行哈希处理。
创建auth模块
让我们首先创建一个 auth 模块,像这样:
nest g module auth
这个命令将创建一个新的文件夹,auth ,并有一个auth.module.ts 文件;它还将更新app.module.ts 文件。
配置JWT
现在,让我们实现一个JSON网络令牌来验证用户进入应用程序。
为了开始,请安装以下依赖项:
npm install --save @nestjs/jwt passport-jwt
npm install --save-dev @types/passport-jwt
接下来,创建一个新文件,local.auth.ts ,并添加以下代码:
import { Strategy } from 'passport-local';
import { PassportStrategy } from '@nestjs/passport';
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { AuthService } from './auth.service';
@Injectable()
export class LocalStrategy extends PassportStrategy(Strategy) {
constructor(private authService: AuthService) {
super();
}
async validate(username: string, password: string): Promise<any> {
const user = await this.authService.validateUser(username, password);
if (!user) {
throw new UnauthorizedException();
}
return user;
}
}
在这里,我们实现了一个护照-本地策略来验证JSON网络令牌。默认情况下,passport-local策略期望在请求体中有username 和password 属性。
我们还实现了validate() 方法,Passport中间件将调用该方法,使用适当的策略特定的参数集验证用户。
接下来,用以下内容替换AuthModule 中的代码:
import { Module } from "@nestjs/common"
import { UserModule } from "src/user/user.module";
import { AuthService } from "./auth.service"
import { PassportModule } from "@nestjs/passport"
import { JwtModule } from '@nestjs/jwt';
import { AuthController } from './auth.controller';
import { UsersService } from "src/user/user.service";
import { MongooseModule } from "@nestjs/mongoose"
import { UserSchema } from "../user/user.model"
import { LocalStrategy } from './local-strategy';
@Module({
imports: [UserModule, PassportModule, JwtModule.register({
secret: 'secretKey',
signOptions: { expiresIn: '60s' },
}), MongooseModule.forFeature([{ name: "user", schema: UserSchema }])],
providers: [AuthService, UsersService, LocalStrategy],
controllers: [AuthController],
})
export class AuthModule { }
在这里,我们将PassportModule 和JwtModule 导入到导入的数组中。然后我们使用register 方法来注册JWT,提供秘密和过期时间。
我们还在导入中提供了UserSchema ,并将UserService 和我们的LocalStrategy 添加到提供者阵列中。
注意事项: 为了安全起见,一定要把你的JWT秘密保存在环境变量中。
创建认证服务和控制器
现在,让我们在应用程序中添加认证功能。
配置好JWT和Passport后,运行以下命令,在auth 文件夹中创建auth.service.ts 和auth.controller.ts 文件:
nest generate service auth
nest generate controller auth
接下来,打开auth/auth.service.ts 文件,用以下代码验证用户:
import { Injectable, NotAcceptableException } from '@nestjs/common';
import { UsersService } from 'src/user/user.service';
import * as bcrypt from 'bcrypt';
import { JwtService } from '@nestjs/jwt';
@Injectable()
export class AuthService {
constructor(private readonly usersService: UsersService, private jwtService: JwtService) { }
async validateUser(username: string, password: string): Promise<any> {
const user = await this.usersService.getUser({ username });
if (!user) return null;
const passwordValid = await bcrypt.compare(password, user.password)
if (!user) {
throw new NotAcceptableException('could not find the user');
}
if (user && passwordValid) {
return user;
}
return null;
}
async login(user: any) {
const payload = { username: user.username, sub: user._id };
return {
access_token: this.jwtService.sign(payload),
};
}
}
在这里,我们创建了validateUser 方法来检查来自user.model 的用户是否与数据库中的用户记录匹配。如果没有匹配,该方法返回一个null 的值。
我们还创建了login 方法,该方法使用jwtService.sign 方法为从我们的validate 中返回的用户生成一个JWT访问令牌LocalStrategy 。
现在,将下面的代码片段添加到auth/auth.controller.ts 文件中,为用户创建一个路由login:
import { Controller, Request, Post, UseGuards } from '@nestjs/common';
import { AuthService } from './auth.service';
import { AuthGuard } from '@nestjs/passport';
@Controller()
export class AuthController {
constructor(private authService: AuthService) { }
@UseGuards(AuthGuard('local'))
@Post('auth/login')
async login(@Request() req) {
return this.authService.login(req.user);
}
}
在这里,我们使用了@UseGuards() 装饰器,在用户请求登录路由时强制执行认证。有了AuthGuard 类,我们就可以使用local 策略来验证用户。
测试应用程序
现在让我们用Postman来测试这个应用程序。我们将从signup route 。
首先,启动该应用程序:
npm run start
接下来,打开Postman,通过向端点发送一个post请求来测试注册路线。localhost:3000/users/signup.

现在,通过向端点发送一个帖子请求来测试登录端点。localhost:3000/auth/login.

如果数据库中存在username 和password ,用户将收到一个access_token ,如上所示。有了access_token ,用户将能够访问API中受保护的路由。
总结
在本教程中,我们提供了NestJS的概述,然后演示了如何在NestJS API上实现JWT用户认证。
现在你有了这些知识,你将如何在你的下一个Nest项目中处理用户认证?要了解更多关于NestJS JWT认证的信息,请参考官方文档。