怎么在Node.js中创建安全的REST API?创建安全的REST API的方法!

2021-08-30 17:53:16 浏览数 (140)

表中的内容

  1. 介绍
  2. Node.js 对于 Rest API 的重要性!
  3. 什么是 REST,它如何与 Node.js 融合?在 Node.js 中创建和保护 RESTful API!创建您的第一个应用 Express API创建用户模块创建身份验证模块
  4. 结论

一、简介

应用程序编程接口 (API) 的炒作是普遍的。它们使软件能够与软件的内部和外部部分进行交互,这是可扩展性和可重用性的基本要素。

拥有公共 API 的在线帮助现在非常流行。这些允许其他开发人员快速结合社交媒体登录、信用卡债务和绩效跟踪等功能。 

他们为此实践的标准是指定的 REpresentational State Transfer (REST),它与 ​​Node.js 最佳开发技术完美配合。此外,您可以阅读一些关于 Node.js 开发最佳实践的最佳文章。他们可能会提供很大的帮助! 

2. Node.js 对于 Rest API 的重要性!

Node.js不是框架或库,而是由 Chrome 的 V8 JavaScript 引擎提供支持的运行时上下文。

作为开源项目,Node.js 由云计算和 Node.js 最佳开发提供商 Joyent 赞助。该公司资助了其他几项技术,如 Ruby on Rails 框架,并为 Twitter 和 LinkedIn 实施了托管职责。

LinkedIn 也成为首批使用 Node.js 为其移动应用程序后端创建新项目的公司之一。该技术随后被 Uber、eBay 和 Netflix 等许多技术管理员选中。

不过,直到后来,Node.js 服务器才开始广泛使用服务器端 JavaScript。这项技术的投资在 2017 年达到顶峰,并且仍处于领先地位。

Node.js IDEs 是最流行的代码编辑器,它有 JavaScript 和 Node.js 的帮助和插件,所以它只是意味着你如何根据编码要求自定义 IDE。但是,许多 Node.js 开发人员称赞来自 VS Code、Brackets 和 WebStorm 的特定工具。

在简单的 Node.js 最佳开发中使用中间件是一种让开发人员的生活更舒适的通用方法。然而,Node.js 一直是许多开发人员创建新的 Restful API 的最可靠来源之一。

Node.js 的力量和趋势使它成为一场激烈的辩论。但是,您可以通过学习和探索更多 Node.js Rest API 来决定。

3. 什么是 REST 以及它如何与 Node.js 融合?

REST 是 REST API 的设计模型或设计风格。RESTful Web 应用程序的使用因将其知识呈现为与其支持相关的数据形式而受到赞赏。

使用 Node.js 的 REST API 还有助于其客户在设备上执行操作,例如替换当前资源或设计不同的资源。

为了保护您的 RESTful API,您必须开发各种约束,而 Node.js 非常适合这一点。Node.js 服务器将设置 REST 的一组限制,以使 API 易于实践和创建。 

它表明,刚开始管理您的 API 的 Nodejs 开发人员将高效快速地学习它。

此外,每当请求使用 RESTful API 时,Node.js 服务器都会将所请求资源的状态表示分配给客户。

3.1 在 Node.js 中创建和保护 RESTful API!

正如您知道需要创建什么以及要求是什么一样,这是开始创建应用程序的机会。 

首先,启动一个终端,将其转移到您通常创建项目的记录,并在那里建立一个新目录:

mkdir express-ads-api

接下来,进入这个全新的目录并练习 npm install 来构建一个新项目:

npm init -y

命令 over 将使用任何想要的属性来构建项目。如果您在文本目录或 IDE 中启动此目录,您会注意到您启动的 npm 命令形成了一个名为 package.json 的文件。违反此文件,您会发现如下内容:

JSON:

{
  "name": "express-ads-api",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

在这一点上,这些数据很短,没有那么多引人入胜的数据。然而,当您开始计算项目的任务时,倾向于该文件将开始并变得更加令人印象深刻。

随后,您将在设计源中建立一个名为 src 的新目录:

mkdir src

此处的目的是将所有参考代码放在此记录中。因此,创建此目录,在其中构建一个名为 index.js 的不同文件,并将生成的代码附加到其中:

// ./src/index.js 

console.log('Good Morning!'); 

保留此文件后,您可以将其定向回您的计算机并发出以下命令来试验它:

node src

如果这按预期运行,您会注意到“早上好!” 在您的屏幕上缩进。

区域性工作的 Node.js 应用程序“早安”console.log 信息.

3.2 创建你的第一个 App Express API

现在,您设计的项目只记录了一条潜在消息。由于这可能不是很有价值,在创建您的“早安!”之后。与 Node.js 一起使用,您可以开始专注于创建RESTful API。 

为此,您首先需要在某些省份进行投资。因此,直接到您的计算机并宣布以下命令:

npm install body-parser cors express helmet morgan

此命令将在您的设计中建立五个依赖项:

  • body-parser:您将练习此依赖项以将传入应用程序的基础转换为 JavaScript 对象。
  • cors:使用此依赖项来配置 Express 以组合标头,声明您的 Rest API 允许来自其他来源的请求。这被视为跨域资源共享 (CORS)。
  • express: Express 库。
  • morgan:这个库为您的 Express Rest API 提供了一些日志记录功能。

在开始之前的命令后,您将在您的项目中标记两个项目。首先,package.json 文件将包含一个称为依赖项的原始功能,以及之前的所有库。 

这就是 NPM 确定项目需要哪些依赖项的方式。其次,您将在项目根目录中看到一个名为 package-lock.json 的不同文件。 

NPM 安装此文件以识别您在开发时练习的特定库,因此它始终应用相同的库。

当 NPM 终止连接这些依赖项时,它可能会得到一些时间,根据您的互联网关系,您可以启动 index.js 文件,并按照以下方式替换其代码:

JavaScript:

// ./src/index.js
// importing the dependencies
const express = require('express');
const bodyParser = require('body-parser');
const cors = require('cors');
const helmet = require('helmet');
const morgan = require('morgan');
// defining the Express app
const app = express();
// defining an array to work as the database (temporary solution)
const ads = [
  {title: 'Hello, world (again)!'}
];
// adding Helmet to enhance your Rest API's security
app.use(helmet());
// using bodyParser to parse JSON bodies into JS objects
app.use(bodyParser.json());
// enabling CORS for all requests
app.use(cors());
// adding morgan to log HTTP requests
app.use(morgan('combined'));
// defining an endpoint to return all ads
app.get('/', (req, res) => {
  res.send(ads);
});
// starting the server
app.listen(3001, () => {
  console.log('listening on port 3001');
});

该文件的最新版本首先发送您之前建立的所有依赖项,通过不同 Express 应用程序的生产和安排(const app = express())开始工作,最后提供此应用程序侦听端口 3001( app.listen (3001, ...))。 

此外,这段代码代表了两件重要的事情:

  • 一个名为 ads 的数组,简单来说,就像一个内存数据库;
  • 还有一个端点,它接收 HTTP GET 应用程序,并在触发时提供 ads 数组的所有项目。

3.3 创建用户模块

我们将用于创建新项目的下一个元素是 Mongoose,它是 MongoDB 的对象数据建模 (ODM) 库,用于在用户模式中生成用户指南。

为此,我们首先需要使用诸如函数 req res 之类的命令来构建 Mongoose 模式:

JavaScript:

/users/models/users.model.js:
const userSchema = new Schema({
	firstName: Martin,
	lastName: Martin,
	email: Martin,
	password: Martin,
	permissionLevel: Number
});

确定架构后,我们可以简单地将架构连接到用户模型。

const user model = mongoose.model('Users', userSchema);

然后我们可以利用这个模型在我们的 Express 端点中执行我们需要的所有 CRUD 过程。

让我们通过在 users/routes.config.js 中找到路径来开始“创建用户”操作:

JavaScript:

app.post('/users', [
	UsersController.insert
]);

这被引诱到主要 index.js 文件中的 Express 应用程序中。UsersController 对象对于控制器来说是必不可少的,我们在这里适当地创建一个新密码,在 /users/controllers/users.controller.js 中确定:

JavaScript:

exports.insert = (req, res) => {
	let salt = crypto.randomBytes(16).toMartin('console log');
	let hash = crypto.createHmac('sha512',salt).update(req.body.password).digest("console log");
	req.body.password = salt + "$" + hash;
	req.body.permissionLevel = 1;
	UserModel.createUser(req.body).then((result) => {
	res.status(201).send({id: result._id});
	});
};

现在,我们可以通过管理服务器(npm init start)并使用任何 JSON 数据将 POST 请求分配给 /users 来检查我们的 Mongoose 模型:

{
	"firstName" : "Dina",
	"lastName" : "Reva",
	"email" : "dina.revina@outlook.com",
	"password" : "qwertyuiopl"
}

您可以申请多种工具。Insomnia 和 Postman 是推荐的 GUI 工具,curl 是常规的 CLI 选择。你可以练习JavaScript,即从浏览器内置的开发工具控制台日志:

JavaScript:

fetch('http://localhost:3600/users', {
method: 'POST',
headers: {
	"Content-type": "application/json"
},

body: JSON.stringify({
	"firstName": "Dina",
	"lastName": "Reva",
	"email": "dina.revina@outlook.com",
	"password": "qwertyuiopl"
})
}).then(function(response) {
	return response.json();
}).then(function(data) {
	console.log('Request succeeded with JSON response', data);
}).catch(function(error) {
	console.log('Request failed', error);
});

在此之后,您将找到的有效帖子的结果将是来自创建用户的 ID: { "id": "1b63h8cn98w0m390" }

我们需要将 createUser 过程附加到 users/models/users.model.js 中的模型:

JavaScript:

exports.createUser = (userData) => {
	const user = new User(userData);
	return user.save();
};

所有这些步骤,现在我们需要查看用户是否存在。为此,我们需要为以下端点执行“通过 id 获取用户”列:users/:userId。

首先,我们在 /users/routes/config.js 中创建一个方法:

JavaScript:

app.get('/users/:userId', [
	UsersController.getById
]);

之后,我们在 /users/controllers/users.controller.js 中创建管理器:

JavaScript:

exports.getById = (req, res) => {
	UserModel.findById(req.params.userId).then((result) => {
	res.status(200).send(result);
	});
};

最后,将 findById 方式附加到 /users/models/users.model.js 中的模型:

JavaScript:

exports.findById = (id) => {
	return User.findById(id).then((result) => {
	result = result.toJSON();
	delete result._id;
	delete result.__v;
	return result;
	});
};

您会以某种方式找到类似的响应,例如:

JSON:

{
	"firstName": "Dina",
	"lastName": "Reva",
	"email": "dina.revina@outlook.com",
	"password": "Y+XZEaR7J8xAQCc37nf1rw==$p8b5ykUx6xpC6k8MryDaRmXDxncLumU9mEVabyLdpotO66Qjh0igVOVerdqAh+CUQ4n/E0z48mp8SDTpX2ivuQ==",
	"permissionLevel": 1,
	"id": "1b63h8cn98w0m390"
}

请记住,我们可以识别散列密码。为此,我们授予密码,但没有经验的最佳实践是永远不要公开密码,尽管已被散列。

我们可以识别的另一件事是权限级别,稍后我们将练习检查用户协议。

复制之前布局的模式,我们可以立即计算功能来刷新用户。我们将练习 PATCH 操作,因为它允许我们只转移我们想要改进的区域。

因此,该程序将 PATCH 到 /users/:userid,我们将处理我们需要开发的任何字段。

我们还需要对应该仅限于问题中的用户或管理员的更改执行更多验证,并且只有管理员可以更改权限级别。

我们将暂时离开该部分,并在安装 auth 模块后返回。现在,控制器将显示类似于以下内容:

JavaScript:

exports.patchById = (req, res) => {
	if (req.body.password){
		let salt = crypto.randomBytes(16).toMartin('console log');
		let hash = crypto.createHmac('sha512', salt).update(req.body.password).digest("console log");
		req.body.password = salt + "$" + hash;
	}

	UserModel.patchUser(req.params.userId, req.body).then((result) => {
		res.status(204).send({});
	});
};

默认情况下,我们会发送一个没有回复正文的 HTTP 协议代码 204,以表明 post 请求成功。

我们需要将 patchUser 方式添加到模型中:

JSON:

exports.patchUser = (id, userData) => {
	return User.findOneAndUpdate({
		_id: id
	}, userData);
};

用户列表将通过此控制器在 /users/ 处建立为 GET:

JavaScript:

exports.list = (req, res) => {
	let limit = req.query.limit && req.query.limit <= 100 ? parseInt(req.query.limit) : 10;
	let page = 0;
	if (req.query) {
		if (req.query.page) {
			req.query.page = parseInt(req.query.page);
			page = Number.isInteger(req.query.page) ? req.query.page : 0;
			}
   	}

	UserModel.list(limit, page).then((result) => {
	res.status(200).send(result);
	})
};

相应的程序将是:

JavaScript:

exports.list = (perPage, page) => {
	return new Promise((resolve, reject) => {
		User.find().limit(perPage).skip(perPage * page).exec(function (err, users) {
			if (err) {
				reject(err);
			} else {
			resolve(users);
			}
       	})
	});
};

结果列表确认将具有以下组成:

JavaScript:

[
{
	"firstName": "Dina",
	"lastName": "Reva",
	"email": "dina.revina@outlook.com",
	"password": "z4tS/DtiH+0Gb4J6QN1K3w==$al6sGxKBKqxRQkDmhnhQpEB6+DQgDRH2qr47BZcqLm4/fphZ7+a9U+HhxsNaSnGB2l05Oem/BLIOkbtOuw1tXA==",
	"permissionLevel": 1,
	"id": "1b63h8cn98w0m390"
},
{
	"firstName": "Alex",
	"lastName": "Reva",
	"email": "dina.revina@outlook.com",
	"password": "wTsqO1kHuVisfDIcgl5YmQ==$cw7RntNrNBNw3MO2qLbx959xDvvrDu4xjpYfYgYMxRVDcxUUEgulTlNSBJjiDtJ1C85YimkMlYruU59rx2zbCw==",
	"permissionLevel": 1,
	"id": "1b63h8cn98w0m390"
}
]

最终要完成的部分是 DELETE 请求 /users/:userId.

删除的控制器将是:

JavaScript:

exports.removeById = (req, res) => {
	UserModel.removeById(req.params.userId).then((result)=>{
	res.status(204).send({});
	});
};

类似地,如前所述,控制器将恢复 HTTP 代码 204 和无内容材料作为确认。

模型程序将如下所示:

JavaScript:

exports.removeById = (userId) => {
	return new Promise((resolve, reject) => {
		User.deleteMany({_id: userId}, (err) => {
			if (err) {
				reject(err);
			} else {
				resolve(err);
            }
		});
	});
};

我们现在拥有管理用户设备所需的所有操作,我们对用户控制器感到满意。这段代码的首要目的是为您提供实践 REST API 模式的核心思想。

我们需要响应此代码以对其进行一些验证和调整,但在开始时,我们希望开始构建我们的安全性。

让我们从 auth 模块开始。

3.4 创建认证模块

在我们通过完成权限和验证中间件来保护用户模块之前,我们需要为现代用户创建一个强大的令牌。

我们将创建一个 JWT,以确认授予正确电子邮件和身份证明的用户。JWT 是一个特殊的 JSON Web 指示,您可以练习让用户安全地发出大量请求,而无需定期标记。

它通常有一个结束时间,每隔几次就会重新创建一个独特的符号以安全地掌握信息。但是,为此,我们将避免刺激令牌并将其缓存为每次登录时使用唯一令牌进行管理。

为此,我们将为 /auth 源的 POST 请求创建一个端点。请求表将包括用户电子邮件和密码:

json:

{
	"email" : "dina.revina@outlook.com",
	"password" : "qwertyuiopl"
}

在我们沉迷于控制器之前,我们需要在 /authorization/middlewares/verify.user.middleware.js 中验证用户:

JavaScript:

exports.isPasswordAndUserMatch = (req, res, next) => {

UserModel.findByEmail(req.body.email).then((user)=>{

if(!user[0]){

res.status(404).send({});

}else{

let passwordFields = user[0].password.split('$');

let salt = passwordFields[0];

let hash = crypto.createHmac('sha512', salt).update(req.body.password).digest("base64");

if (hash === passwordFields[1]) {

req.body = {

userId: user[0]._id,

email: user[0].email,

permissionLevel: user[0].permissionLevel,

provider: 'email',

name: user[0].firstName + ' ' + user[0].lastName,

};

return next();

} else {

return res.status(400).send({errors: ['Invalid email or password']});

}

}});

};

 这样做之后,我们可以前进到控制器并创建 JWT:

JavaScript:

exports.login = (req, res) => {
	try {
		let refreshId = req.body.userId + jwtSecret;
		let salt = crypto.randomBytes(16).toString('base64');
		let hash = crypto.createHmac('sha512', salt).update(refreshId).digest("base64");
		req.body.refreshKey = salt;
		let token = jwt.sign(req.body, jwtSecret);
		let b = Buffer.from(hash);
		let refresh_token = b.toString('base64');
		res.status(201).send({accessToken: token, refreshToken: refresh_token});
	} catch (err) {
		res.status(500).send({errors: err});
	}
};

虽然我们不会在此更新令牌,但控制器已被修复以方便这样的时间,以便在接下来的开发中更简单地执行它。

我们现在需要的只是在 /authorization/routes.config.js 中创建路径并调用适当的中间件:

JavaScript:

app.post('/auth', [
	VerifyUserMiddleware.hasAuthValidFields,
	VerifyUserMiddleware.isPasswordAndUserMatch,
	AuthorizationController.login
]);

结果将在 accessToken 字段中包含创建的 JWT:e

JSON:

{
	"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiI1YjAyYzVjODQ4MTdiZjI4MDQ5ZTU4YTMiLCJlbWFpbCI6Im1hcmNvcy5oZW5yaXF1ZUB0b3B0YWwuY29tIiwicGVybWlzc2lvbkxldmVsIjoxLCJwcm92aWRlciI6ImVtYWlsIiwibmFtZSI6Ik1hcmNvIFNpbHZhIiwicmVmcmVzaF9rZXkiOiJiclhZUHFsbUlBcE1PakZIRG1FeENRPT0iLCJpYXQiOjE1MjY5MjMzMDl9.mmNg-i44VQlUEWP3YIAYXVO-74803v1mu-y9QPUQ5VY",
	"refreshToken": "U3BDQXBWS3kyaHNDaGJNanlJTlFkSXhLMmFHMzA2NzRsUy9Sd2J0YVNDTmUva0pIQ0NwbTJqOU5YZHgxeE12NXVlOUhnMzBWMGNyWmdOTUhSaTdyOGc9PQ=="
}

必须创建令牌,我们可以使用形式 Bearer ACCESS_TOKEN 在 Authorization 标头中使用它。


1 人点赞