如何在Node.js中使用JWT对用户进行身份验证和授权

认证和授权是计算机安全的基本概念。您使用凭据(如用户名和密码)来证明您的身份并将自己标识为注册用户,然后获得额外的特权。

当您使用Facebook或Google帐户登录在线服务时,也适用此概念。

在本文中,我们将使用JWT(JSON Web Tokens)身份验证构建一个Nodejs API。本教程中要使用的工具有:

  • Expressjs
  • MongoDB数据库
  • Mongoose
  • Dotenv
  • Bcryptjs
  • Jsonwebtoken

认证与授权

什么是认证?

认证是通过获取凭据(如电子邮件、密码和令牌)来识别用户的过程。给定的凭据与注册用户的凭据进行比较,这些凭据可在本地计算机系统的文件或任何数据库中找到。如果给定的凭据与数据库中的可用数据匹配,认证过程就完成了,用户就被允许访问资源。

什么是授权?

授权发生在认证之后。每个授权都必须有一个认证过程。它是允许用户从系统或网站上访问资源的过程。在本教程中,我们将授权已登录用户访问用户数据。如果用户未登录,则无法访问数据。

授权的最佳例子是Facebook和Twitter等社交媒体平台。如果没有账户,您将无法访问社交媒体内容。

授权的另一个例子是基于订阅的内容,您可以通过登录网站进行认证,但在未订阅之前,您将无权访问内容。

先决条件

在继续之前,我假设您对Javascript和MongoDB有基本的了解,并且对Nodejs有很好的了解。

确保您已在本地计算机上安装了node和npm。要检查node和npm是否已安装在您的计算机上,请打开命令提示符并键入node -v和npm -v。结果应如下所示。

您的版本可能与我的不同。npm会随node一起自动下载。如果您还没有下载,请从NodeJS website下载。

您需要一个IDE(集成开发环境)来编写代码。在本教程中,我使用的是VS code editor。如果您有其他IDE,也可以使用。如果您的计算机上没有安装任何IDE,您可以从Visual Studio website下载。根据您的本地系统进行下载。

项目设置

在您的本地计算机上的任何位置创建一个名为nodeapi的文件夹,然后使用vs-code打开它。打开vs-code终端,然后通过键入以下内容来初始化node包管理器。

npm init -y

确保您在nodeapi目录下。

上述命令将创建一个package.json文件,其中包含我们在此项目中要使用的所有依赖项。

现在,我们将下载上面提到的所有软件包,现在在终端中键入并输入它们。

npm install express dotenv jsonwebtoken mongoose bcryptjs

现在,您将拥有如下所示的文件和文件夹。

创建服务器并连接数据库

现在创建一个名为index.js的文件和一个名为config的文件夹。在config文件夹内,创建两个名为conn.jsconfig.env的文件。分别写入以下代码。

index.js

const express = require('express');
const dotenv = require('dotenv');

// 使用其他库和文件配置dotenv文件
dotenv.config({path:'./config/config.env'}); 

// 从express中创建一个app
const app = express();

// 使用express.json获取json数据的请求
app.use(express.json());



// 监听服务器
app.listen(process.env.PORT,()=>{
    console.log(`Server is listening at ${process.env.PORT}`);
})

如果您使用dotenv,则在调用使用环境变量的其他文件之前,在index.js文件中进行配置。

conn.js

const mongoose = require('mongoose');

mongoose.connect(process.env.URI, 
    { useNewUrlParser: true,
     useUnifiedTopology: true })
    .then((data) => {
        console.log(`Database connected to ${data.connection.host}`)
})

config.env

URI = 'mongodb+srv://ghulamrabbani883:[email protected]/?retryWrites=true&w=majority'
PORT = 5000

我使用的是mongo-DB Atlas URI,您也可以使用localhost。

创建模型和路由

模型是Mongo-DB数据库中数据的布局,将作为JSON文档存储。要创建模型,我们将使用mongoose schema。

路由是应用程序如何响应客户端请求的方式。我们将使用express router函数创建路由。

路由方法通常需要两个参数。第一个是路由,第二个是回调函数,用于定义当客户端请求时该路由应该做什么。

当需要时,它还接受第三个参数作为中间件函数,例如在身份验证过程中。由于我们正在构建经过身份验证的API,我们还将使用中间件函数来授权和验证用户。

现在我们将创建两个名为routesmodels的文件夹。在routes文件夹内,创建一个名为userRoute.js的文件,在models文件夹内,创建一个名为userModel.js的文件。创建文件后,在各自的文件中写入以下代码。

userModel.js

const mongoose = require('mongoose');

// 使用mongoose创建模式
const userSchema = new mongoose.Schema({
    name: {
        type:String,
        required:true,
        minLength:[4,'名称应至少为4个字符']
    },
    email:{
        type:String,
        required:true,
        unique:true,
    },
    password:{
        type:String,
        required:true,
        minLength:[8,'密码应至少为8个字符']
    },
    token:{
        type:String
    }
})

// 创建模型
const userModel = mongoose.model('user',userSchema);
module.exports = userModel;

userRoute.js

const express = require('express');
// 创建express路由器
const route = express.Router();
// 导入userModel
const userModel = require('../models/userModel');

// 创建注册路由
route.post('/register',(req,res)=>{

})
// 创建登录路由
route.post('/login',(req,res)=>{

})

// 创建用户路由以获取用户数据
route.get('/user',(req,res)=>{

})

实现路由功能并创建JWT令牌

什么是JWT?

JSON web tokens (JWT) 是一个javascript库,用于创建和验证令牌。它是一个开放标准,用于在客户端和服务器之间共享信息。我们将使用JWT的两个函数。第一个函数是sign,用于创建新的令牌,第二个函数是verify,用于验证令牌。

什么是bcryptjs?

Bcryptjs是由Niels Provos和David Mazières创建的哈希函数。它使用哈希算法对密码进行哈希。在这个项目中,我们将使用两个最常见的bcryptjs函数。第一个bcryptjs函数是hash 用于生成哈希值,第二个函数是compare 函数用于比较密码。

实现路由功能

路由中的回调函数接受三个参数,requestresponse next函数。next参数是可选的,只有在需要时才传递。这些参数应按照请求、响应 next 的顺序排列。现在使用以下代码修改userRoute.jsconfig.envindex.js文件。

userRoute.js

//Requiring all the necessary files and libraries
const express = require(‘express');
const bcrypt = require(‘bcryptjs');
const jwt = require(‘jsonwebtoken');

//Creating express router
const route = express.Router();
//Importing userModel
const userModel = require(‘../models/userModel');

//Creating register route
route.post(“/register”, async (req, res) => {

try {
const { name, email, password } = req.body;
//Check emptyness of the incoming data
if (!name || !email || !password) {
return res.json({ message: ‘请输入所有细节' })
}

//Check if the user already exist or not
const userExist = await userModel.findOne({ email: req.body.email });
if (userExist) {
return res.json({ message: ‘已存在使用给定电子邮件地址的用户' })
}
//Hash the password
const salt = await bcrypt.genSalt(10);
const hashPassword = await bcrypt.hash(req.body.password, salt);
req.body.password = hashPassword;
const user = new userModel(req.body);
await user.save();
const token = await jwt.sign({ id: user._id }, process.env.SECRET_KEY, {
expiresIn: process.env.JWT_EXPIRE,
});
return res.cookie({ ‘token': token }).json({ success: true, message: ‘用户注册成功', data: user })
} catch (error) {
return res.json({ error: error });
}

})
//Creating login routes
route.post(‘/login', async (req, res) => {
try {
const { email, password } = req.body;
//Check emptyness of the incoming data
if (!email || !password) {
return res.json({ message: ‘请输入所有细节' })
}
//Check if the user already exist or not
const userExist = await userModel.findOne({email:req.body.email});
if(!userExist){
return res.json({message:'凭证错误'})
}
//Check password match
const isPasswordMatched = await bcrypt.compare(password,userExist.password);
if(!isPasswordMatched){
return res.json({message:'凭证错误 pass'});
}
const token = await jwt.sign({ id: userExist._id }, process.env.SECRET_KEY, {
expiresIn: process.env.JWT_EXPIRE,
});
return res.cookie({“token”:token}).json({success:true,message:'成功登录'})
} catch (error) {
return res.json({ error: error });
}

})

//Creating user routes to fetch users data
route.get(‘/user', async (req, res) => {
try {
const user = await userModel.find();
if(!user){
return res.json({message:'未找到用户'})
}
return res.json({user:user})
} catch (error) {
return res.json({ error: error });
}
})

module.exports = route;

什么是中间件?

中间件是一个函数,它可以访问请求-响应周期中的请求响应 对象next函数。当函数执行完成时,将调用next函数。如上所述,当您必须执行另一个回调函数或中间件函数时,请使用next()。

现在创建一个名为middleware的文件夹,在其中创建名为auth.js的文件,并编写以下代码。

auth.js

const userModel = require('../models/userModel');
const jwt = require('jsonwebtoken');
const isAuthenticated = async (req,res,next)=>{
    try {
        const {token} = req.cookies;
        if(!token){
            return next('请登录以访问数据');
        }
        const verify = await jwt.verify(token,process.env.SECRET_KEY);
        req.user = await userModel.findById(verify.id);
        next();
    } catch (error) {
       return next(error); 
    }
}

module.exports = isAuthenticated;

现在安装cookie-parser库以配置cookieParser。cookieParser帮助您访问存储在cookie中的令牌。如果您的nodejs应用程序中没有配置cookieParser,则无法从请求对象的标头访问cookie。现在,在终端中输入以下内容来下载cookie-parser。

npm i cookie-parser

现在,您安装了cookieParser。通过修改index.js文件配置您的应用程序,并将中间件添加到“/user/”路由中。

index.js文件

const cookieParser = require('cookie-parser');
const express = require('express');
const dotenv = require('dotenv');

//使用任何其他库和文件配置dotenv文件
dotenv.config({path:'./config/config.env'}); 
require('./config/conn');
//从express创建一个应用程序
const app = express();
const route = require('./routes/userRoute');

//使用express.json来获取json数据的请求
app.use(express.json());
//配置cookie-parser
app.use(cookieParser()); 

//使用路由
app.use('/api', route);

//监听服务器
app.listen(process.env.PORT,()=>{
    console.log(`服务器在${process.env.PORT}上监听`);
})

userRoute.js

//引入所有必要的文件和库
const express = require('express');
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');
const isAuthenticated = require('../middleware/auth');

//创建express路由器
const route = express.Router();
//导入userModel
const userModel = require('../models/userModel');

//创建用户路由以获取用户数据
route.get('/user', isAuthenticated, async (req, res) => {
    try {
        const user = await userModel.find();
        if (!user) {
            return res.json({ message: '找不到用户' })
        }
        return res.json({ user: user })
    } catch (error) {
        return res.json({ error: error });
    }
})

module.exports = route;

只有当用户登录时才能访问“/user”路由。

在POSTMAN上检查API

在检查API之前,您需要修改package.json文件。添加以下代码行。

"scripts": {
    "test": "echo "Error: no test specified" && exit 1",
    "start": "node index.js",
    "dev": "nodemon index.js"
  },

您可以通过键入npm start来启动服务器,但这只会运行一次。为了在更改文件时保持服务器运行,您将需要nodemon。通过在终端中输入以下内容来下载它

npm install -g nodemon

-g标志将在您的本地系统上全局下载nodemon。您无需为每个新项目再次下载它。

要运行服务器,请在终端中输入npm run dev。您将获得以下结果。

最后,您的代码已完成,并且服务器正常运行,请转到postman并检查是否正常工作。

什么是POSTMAN?

POSTMAN是一种用于设计、构建、开发和测试API的软件工具。

如果您尚未在计算机上下载postman,请从postman website下载。

现在打开postman,并创建一个名为nodeAPItest的集合,在其中创建三个请求:register,loginuser。您应该有以下文件。

当您将JSON数据发送到“localhost:5000/api/register”时,您将获得以下结果。

由于我们在注册过程中创建并保存令牌到cookie中,因此当您请求“localhost:5000/api/user”路由时,您可以获得用户详细信息。您可以在POSTMAN上检查其余请求。

如果您想获取完整的代码,可以从我的github account获取。

结论

在本教程中,我们学习了如何使用JWT令牌对NodeJS API进行身份验证。我们还授权用户访问用户数据。

编码愉快!

类似文章