Nodejs - 9步开启JWT身份验证

本文翻译自 9 Steps for JWT Authentication in Node.js Application,作者:Shefali, 略有删改。

身份验证是Web开发的重要组成部分。JSON Web令牌(JWT)由于其简单性,安全性和可扩展性,已成为在Web应用程序中实现身份验证的流行方法。在这篇文章中,我将指导你在Node.js应用程序中使用MongoDB进行数据存储来实现JWT身份验证。

在开始之前,我假设你已经安装了Node.js、MongoDB和VS Code,并且你知道如何创建MongoDB数据库和基本的RESTful API。

什么是JWT认证?

JWT身份验证依赖于JSON Web令牌来确认Web应用中用户的身份。JSON Web令牌是使用密钥对进行数字签名的编码JSON对象。

简而言之,JWT身份验证就像为网站提供一个密码。一旦你登录成功,你就得到了这个密码。

JSON Web Token由三部分组成,由点(.)分隔:

  • Header
  • Payload
  • Signature

以下是JWT的基本结构:

sh 复制代码
xxxx.yyyy.zzzz
  • Header:这部分包含有关令牌的信息,如其类型和如何保护。
  • Payload:这部分包含关于用户的声明,如用户名或角色。
  • Signature:确保令牌的完整性,并验证它没有被更改,这可以确保代码安全,不会被篡改。

当你登录成功时,你会得到这个代码。每次你想访问某个数据时,你都要携带这个代码来证明是你。系统会检查代码是否有效,然后让你获取数据!

接下来让我们看看在node.js项目中进行JWT身份验证的步骤。

步骤1:新建项目

首先为您的项目创建一个新目录,并使用以下命令进入到该目录。

sh 复制代码
mkdir nodejs-jwt-auth
cd nodejs-jwt-auth

通过在终端中运行以下命令初始化项目(确保您位于新创建的项目文件夹中)。

sh 复制代码
npm init -y

接下来通过以下命令安装必要的依赖项:

sh 复制代码
npm install express mongoose jsonwebtoken dotenv

上面的命令将安装:

  • express: 用于构建Web服务器。
  • mongoose:MongoDB的数据库。
  • jsonwebtoken:用于生成和验证JSON Web令牌(JWT)以进行身份验证。
  • dotenv:用于从.env文件加载环境变量。

现在您的package.json文件应该看起来像这样:

步骤2:连接MongoDB数据库

要连接MongoDB数据库,请查看以下链接中的具体操作流程。

https://shefali.dev/restful-api/#Step_4_Creating_a_MongoDB_Database

步骤3:创建 .env 文件

为了 MongoDB 连接地址的安全,让我们在根目录下创建一个名为 .env 的新文件。

将以下代码添加到.env文件中。

sh 复制代码
MONGODB_URL=<Your MongoDB Connection String>
SECRET_KEY="your_secret_key_here"

<Your MongoDB Connection String>替换为您从MongoDB Atlas获得的连接字符串(在步骤2中),并将your_secret_key_here替换为您想要的密钥字符串。现在你的.env文件应该是这样的。

ini 复制代码
MONGODB_URL='mongodb+srv://shefali:********@cluster0.sscvg.mongodb.net/nodejs-jwt-auth'
SECRET_KEY="ThisIsMySecretKey"

MONGODB_URL最后我们加入node.js-jwt-auth,这是我们的数据库名称。

步骤4:Express

在根目录下创建一个名为index.js的文件,并将以下代码添加到该文件中。

js 复制代码
const express = require("express");
const mongoose = require("mongoose");

require("dotenv").config(); //for using variables from .env file.

const app = express();
const port = 3000;

//middleware provided by Express to parse incoming JSON requests.
app.use(express.json()); 

mongoose.connect(process.env.MONGODB_URL).then(() => {
  console.log("MongoDB is connected!");
});

app.get("/", (req, res) => {
  res.send("Hello World!");
});

app.listen(port, () => {
  console.log(`Server is listening on port ${port}`);
});

现在我们可以通过以下命令运行服务器。

sh 复制代码
node index.js

输出应如下图所示。

通过使用命令node index.js,您必须在每次更改文件时重新启动服务器。为了避免这种情况,您可以使用以下命令安装nodemon

sh 复制代码
npm install -g nodemon

现在使用下面的命令运行服务器,它会在每次更改文件时自动重新启动服务器。

sh 复制代码
nodemon index.js

步骤5:创建用户数据库模型

在根目录下创建一个名为models的新目录,并在其中创建一个名为User.js的新文件。

现在让我们为我们的项目创建一个简单的模型,将以下代码添加到User.js文件中。

js 复制代码
const mongoose = require("mongoose");

const userSchema = new mongoose.Schema({
  username: {
    type: String,
    required: true,
    unique: true,
  },
  password: {
    type: String,
    required: true,
  },
});

module.exports = mongoose.model("User", userSchema);

步骤6:实现身份验证路由

在根目录中,创建一个名为routes的新目录,并在其中创建一个名为auth.js的文件。

然后将以下代码添加到该文件中:

js 复制代码
const express = require("express");
const jwt = require("jsonwebtoken");
const User = require("../models/User");
const router = express.Router();

// Signup route
router.post("/signup", async (req, res) => {
  try {
    const { username, password } = req.body;
    const user = new User({ username, password });
    await user.save();
    res.status(201).json({ message: "New user registered successfully" });
  } catch (error) {
    res.status(500).json({ message: "Internal server error" });
  }
});

// Login route
router.post("/login", async (req, res) => {
  const { username, password } = req.body;
  try {
    const user = await User.findOne({ username });

    if (!user) {
      return res.status(401).json({ message: "Invalid username or password" });
    }
    if (user.password !== password) {
      return res.status(401).json({ message: 'Invalid username or password' });
    }
    // Generate JWT token
    const token = jwt.sign(
      { id: user._id, username: user.username },
      process.env.SECRET_KEY
    );
    res.json({ token });
  } catch (error) {
    res.status(500).json({ message: "Internal server error" });
  }
});

module.exports = router;

分解上面的代码:

导入依赖:

js 复制代码
const express = require("express");
const jwt = require("jsonwebtoken");
const User = require("../models/User");
const router = express.Router();

在这里,我们导入以下依赖项:

  • express: 用于构建Web服务器。
  • jsonwebtoken:用于生成和验证JSON Web令牌(JWT)以进行身份验证。
  • User:从第5步中创建的User模块导入的模型。
  • router:Express中的Router()函数用于单独定义路由,然后将其合并到主应用程序中。

注册路由:

js 复制代码
// Signup route
router.post("/signup", async (req, res) => {
  try {
    const { username, password } = req.body;
    const user = new User({ username, password });
    await user.save();
    res.status(201).json({ message: "New user registered successfully" });
  } catch (error) {
    res.status(500).json({ message: "Internal server error" });
  }
});
  • 此路由监听对/signup的POST请求。
  • 当接收到请求时,它从请求体中提取usernamepassword
  • 然后使用提供的用户名和密码创建User模型的一个新实例。
  • 调用save()方法将新用户保存到数据库。
  • 如果用户成功保存,它会返回一个状态码201和一个JSON消息,表示"新用户注册成功"。
  • 如果在此过程中发生错误,它会捕获错误并以状态代码500和错误消息"内部服务器错误"进行响应。

登录路由:

js 复制代码
// Login route
router.post("/login", async (req, res) => {
  const { username, password } = req.body;
  try {
    const user = await User.findOne({ username });

    if (!user) {
      return res.status(401).json({ message: "Invalid username or password" });
    }
    if (user.password !== password) {
      return res.status(401).json({ message: 'Invalid username or password' });
    }
    // Generate JWT token
    const token = jwt.sign(
      { id: user._id, username: user.username },
      process.env.SECRET_KEY
    );
    res.json({ token });
  } catch (error) {
    res.status(500).json({ message: "Internal server error" });
  }
});
  • 此路由监听对/loginPOST请求。
  • 当接收到请求时,它从请求体中提取usernamepassword
  • 然后在数据库中使用提供的username搜索用户。
  • 如果没有找到用户,它会返回一个状态码401(未经授权)和一个JSON消息,指示用户名或密码无效。
  • 如果找到用户,它会检查提供的password是否与数据库中存储的密码匹配。
  • 如果密码不匹配,它会返回一个状态码401(未经授权)和一个JSON消息,指示用户名或密码无效。
  • 如果密码匹配,它将使用jwt.sign()生成一个JWT。
  • 生成的令牌然后作为JSON响应发送。
  • 如果在此过程中出现错误,它会捕获错误并以状态代码500和错误消息"内部服务器错误"进行响应。

最后路由被导出以在index.js文件中使用。

js 复制代码
module.exports = router;

步骤7:使用中间件保护路由

在根目录中,创建一个名为middleware.js的新文件,并将以下代码添加到该文件中。

js 复制代码
const jwt = require("jsonwebtoken");

function verifyJWT(req, res, next) {
  const token = req.headers["authorization"];

  if (!token) {
    return res.status(401).json({ message: "Access denied" });
  }

  jwt.verify(token, process.env.SECRET_KEY, (err, data) => {
    if (err) {
      return res.status(401).json({ message: "Failed to authenticate token" });
    }
    req.user = data;
    next();
  });
}

module.exports = verifyJWT;

此代码是一个中间件函数,用于在应用程序中验证JSON Web令牌(JWT)。

分解上面的代码:

  • 在第一行中,我们导入jsonwebtoken库。
  • 然后定义verifyJWT中间件函数,它有三个参数:req(请求对象)、res(响应对象)和next(下一个中间件函数)。
  • 在中间件函数内部,它首先从请求头中提取token令牌。
  • 如果请求头中没有令牌,它将返回401(未经授权)状态沿着JSON响应,指示"拒绝访问"。
  • 如果存在令牌,它会尝试使用jwt.verify()进行验证。如果验证失败,它会捕获错误并返回一个401状态,其中包含一个JSON响应,指示"Failed to authenticate token"。
  • 如果令牌被成功验证,它将解码的令牌数据附加到req.user对象。
  • 最后导出verifyJWT函数,以便它可以用作应用程序其他部分的中间件。

第8步:验证JWT

现在要验证JWT,请修改index.js,如下所示:

js 复制代码
const express = require('express');
const authRouter = require('./routes/auth');
const mongoose = require("mongoose");
const verifyJWT = require("./middleware")

require("dotenv").config(); //for using variables from .env file.

const app = express();
const PORT = 3000;

mongoose.connect(process.env.MONGODB_URL).then(() => {
    console.log("MongoDB is connected!");
});
app.use(express.json());

//Authentication route
app.use('/auth', authRouter);

//decodeDetails Route
app.get('/decodeDetails', verifyJWT, (req, res) => {
  const { username } = req.user;
  res.json({ username });
});

app.listen(PORT, () => {
  console.log(`Server is running on port ${PORT}`);
});

在上面的代码中,/auth路由是authRouter处理,其中包含的终端用户认证,例如登录和注册。

js 复制代码
app.get('/decodeDetails', verifyJWT, (req, res) => {
  const { username } = req.user;
  res.json({ username });
});
  • 当向/decodeDetails发出请求时,verifyJWT中间件验证附加到请求的JWT令牌。
  • 如果令牌有效,则中间件从req.user中存储的解码令牌数据中提取username
  • 最后路由处理程序发送一个JSON响应,其中包含从令牌中提取的username

步骤9:测试API

注册

http://localhost:3000/auth/signup发送一个POST请求,其中包含Headers Content-Type : application/json和以下JSON主体:

json 复制代码
{
    "username": "shefali",
    "password": "12345678"
}

在响应中,您将看到消息"新用户注册成功"。

登录

http://localhost:3000/auth/login发送一个POST请求,其中包含Header Content-Type : application/jsonJSON主体以及用户名和密码,这是您在注册路由中创建的。

json 复制代码
{
    "username": "shefali",
    "password": "12345678"
}

在响应中,您将收到一个令牌。记下这个令牌,因为在测试decodeDetails路由时需要它。

decodeDetails

http://localhost:3000/decodeDetails发送一个GET请求,并带有一个带有令牌值的Authorization头(您在测试登录路由时得到了它)。

在响应中,您将获得用户名。恭喜你!🎉

您已经在Node.js应用程序中成功实现了JWT身份验证。这种方法提供了一种安全有效的方式来验证Web应用程序中的用户。


看完本文如果觉得有用,记得点个赞支持,收藏起来说不定哪天就用上啦~

专注前端开发,分享前端相关技术干货,公众号:南城大前端(ID: nanchengfe)

相关推荐
写不来代码的草莓熊几秒前
vue前端面试题——记录一次面试当中遇到的题(3)
前端·javascript·vue.js
堇未央1 分钟前
求助,无法获取到最新的数据
后端
福大大架构师每日一题2 分钟前
2025-10-11:求出数组的 X 值Ⅰ。用go语言,给定一个只包含正整数的数组 nums 和一个正整数 k。 你可以进行一次删除操作:在数组两端各自选取一段
后端
Arva .9 分钟前
电子书查询列表接口开发
后端
道可到11 分钟前
写了这么多代码,你真的在进步吗??—一个前端人的反思与全栈突围路线
前端
间彧11 分钟前
在Spring Cloud Gateway中,如何自定义GlobalFilter实现动态路由规则?
后端
间彧12 分钟前
在微服务架构中,过滤器与网关(如Spring Cloud Gateway)如何配合使用?
后端
洛克大航海13 分钟前
Ajax基本使用
java·javascript·ajax·okhttp
用户9163574409515 分钟前
LeetCode热题100——11.盛最多水的容器
javascript·算法
间彧16 分钟前
SpringBoot过滤器详解与项目实战
后端