如何在 Ubuntu 20.04 上使用 Nginx 对 Node.js 应用程序进行评级限制

作者选择了 多样性在技术基金作为 写给捐款计划的一部分接受捐款。

介绍

速度限制器对于生产环境至关重要,因为它们有助于防止恶意粗力攻击访问敏感资源,这些资源通常被放置在网页登录后面,它们在阻止分布式拒绝服务(DDoS)攻击方面也起着积极作用。

在本教程中,您将设置 Nginx 以限制 Node.js 简化登录的流量数量,您将配置 Nginx 以定义一段时间内的最大请求数量,测试率限制器,并定制向用户显示的 429 错误 页面。

前提条件

要完成本教程,您将需要:

  • 一个运行Node.js的Ubuntu 20.04服务器,配备1 CPU和1GB的RAM,配置了一个拥有"sudo"权限和防火墙的非root用户. 注意Nginx 速率限制器将受益于在内存上拥有更多可用的CPU. 要设置非根基用户并配置防火墙,请参见指南 [Initial Server setup with Ubuntu 20.04] (https://andsky.com/tech/tutorials/initial-server-setup-with-ubuntu-20-04).
  • 在您的服务器上设置了应用程序管理器PM2的节点.js应用程序,您可以通过遵循教程来完成,如何在Ubuntu 20.04上设置生产节点.js应用程序.
  • Nginx 安装了 SSL 证书, 您可以遵循教程, [如何在 Ubuntu 20. 04 上安装 Nginx (https://andsky.com/tech/tutorials/how-to-install-nginx-on-ubuntu-20-04) 和 [如何在 Ubuntu 20. 04 上加密 Nginx (https://andsky.com/tech/tutorials/how-to-secure-nginx-with-let-s-encrypt-on-ubuntu-20-04 ) 。
  • 一个已注册域. 您可以免费从 [Freenom] (https://www.freenom.com/) 中获取一个,或者使用您拥有的现有域名.
  • 一个DNS'A'记录,包含您的应用程序和域名,指向您Nginx 速率限制应用程序的IPv4地址. 遵循指南如何从普通域注册员中指向数字海洋命名员设置此功能. 此教程将使用 login. your_ domain.com
  • 对 HTML 和 CSS 有一定的熟悉,将有助于构建登录和错误的网页. 如果您想更多地了解HTML和CSS,请考虑访问如何用HTML构建一个网站和[如何用CSS构建一个网站(https://www.digitalocean.com/community/tutorial_series/how-to-build-a-website-with-css)来学习更多. (英语)

步骤 1 – 设置 Node.js 应用程序

在本教程中,您将使用一个 Node.js 应用程序,该应用程序将是一个位于限制率背后的登录。

作为第一步,您将安装项目的依赖性,创建登录表单(有可选的样式),并测试应用程序是否正在运行。

使用以下命令为应用程序文件创建一个新文件夹:

1sudo mkdir /srv/rate-limited-login

将文件夹的所有权分配给您的非根用户, sammy,您在前提教程中创建的:

1sudo chown sammy /srv/rate-limited-login

由于 sammy现在拥有该文件夹,您不需要每次使用sudo来创建和修改目录中的文件。

切换到您创建的 /srv/rate-limited-login 目录:

1cd /srv/rate-limited-login

启动一个新的 npm 存储库:

1npm init

输入 npm 请求的信息(或按 Enter 来留下默认值)。

在继续编写任何代码之前,您需要安装和保存 Express 作为 npm 依赖,以帮助在 Node.js 应用程序中路由 HTTP 请求:

1npm install express --save

Express 将安装在node_modules文件夹下,由 npm 管理,使用--save旗帜将添加 Express 到package.json配置文件中。

通过将 Express 设置为依赖性,您现在将创建一个 HTTP 路线,该路线将在本文中稍后放置为限额。 项目文件夹, /srv/rate-limited-login,将包含源代码,使 Express 能够处理 HTTP 请求登录目的,以及为包含登录表单本身的网站提供服务。

  • index.js 将托管与 Express 端点相关的代码,以服务静态文件并处理 POST / 请求到您的登录应用程序路线。
  • public/index.html 将包含 HTML 登录表格。

首先,创建和编辑index.js文件:

1nano index.js

添加下面的代码来创建一个应用程序来服务HTTP路径POST /和从端口8080上的公共文件夹的静态文件:

 1[label /srv/rate-limited-login/index.js]
 2const express = require('express');
 3const path = require('path');
 4const app = express();
 5const listeningPort = 8080;
 6
 7app.use('/', express.static(path.join(__dirname, 'public')));
 8
 9app.post('/', (request, response) => {
10    response.send("Logging in...");
11});
12
13app.listen(listeningPort, () => {
14    console.log(`Rate Limited Login listening on port ${listeningPort}!`);
15});

Express 每次使用 Node.js 执行 index.js 时都会处理 HTTP 请求,每个存储在 /srv/rate-limited-login/public’ 下的文件都会通过 Web 服务器访问,而 HTTP POST 请求到 http://localhost:8080` 将返回一个网站,其中有 ** 登录...**。

接下来,创建一个公共文件夹,以托管登录表格的HTML和CSS:

1mkdir /srv/rate-limited-login/public

移动到新文件夹:

1cd /srv/rate-limited-login/public

创建一个名为 index.html 的文件:

1nano index.html

添加一个HTML表格到您的登录文件:

 1[label /srv/rate-limited-login/public/index.html]
 2<html>
 3
 4    <head>
 5        <title>Login</title>
 6        <link href="https://fonts.googleapis.com/css2?family=Quicksand:wght@600&display=swap" rel="stylesheet">
 7        <link rel="stylesheet" href="login.css">
 8    </head>
 9
10    <body>
11        <div id="login-container">
12            <h1>Rate Limited Login</h1>
13            <form id="login-form" method="POST">
14                <label for="user">Username</label>
15                <input type="text" name="user" id="user">
16                <label for="password">Password</label>
17                <input type="password" name="password" id="password">
18                <button type="submit">Login</button>
19            </form>
20        </div>
21    </body>
22
23</html>

现在你已经创建了一个登录表格,每当用户填写它并按下提交按钮时,将向你之前在index.js文件中配置的POST /路线发射请求。

如果你想添加风格,你可以创建一个login.css文件:

1nano login.css

使用以下代码,您可以将梯度添加为背景,更改默认字体,中心文本,并允许身体占用浏览器高度的100%:

1[label /srv/rate-limited-login/public/login.css]
2body {
3    background: linear-gradient(132deg, rgba(184,231,209,1) 35%, rgba(0,255,190,1) 100%);
4    font-family: 'Quicksand', sans-serif;
5    overflow: hidden;
6    height:100%;
7    text-align: center;
8}

您可以增加主标题文本的大小:

1[label /srv/rate-limited-login/public/login.css]
2...
3h1 {
4    font-size: 1.5em;
5}
6...

您可以将梯度添加为登录表单容器的背景,同时添加宽度、垫和边缘:

1[label /srv/rate-limited-login/public/login.css]
2...
3#login-container {
4    background: linear-gradient(132deg, rgba(225,255,249,1) 35%, rgba(182,230,231,1) 100%);
5    margin: 2em auto;
6    padding: 1em;
7    width: 25em;
8}
9...

若要将标签放置在表单的左侧,您可以将体中心的文本叠加:

1[label /srv/rate-limited-login/public/login.css]
2...
3#login-form label {
4    display: block;
5    font-size: 1.1em;
6    margin-left: 1.2em;
7    text-align: left;
8}
9...

最后,您可以为表单字段定义边界、行高度、宽度和间隔:

 1[label /srv/rate-limited-login/public/login.css]
 2...
 3#login-form input, button{
 4    border: 0.05em solid rgb(184,231,209);
 5    font-size: 1em;
 6    line-height: 1.7em;
 7    margin: 1em 1em 1.5em 1em;
 8    padding: 0.2em;
 9    width: 90%;
10}
11...

您现在拥有运行登录应用程序所需的所有文件。

使用此命令启动应用程序:

1node /srv/rate-limited-login/index.js

结果将看起来像这样:

1[secondary_label Output]
2Rate Limited Login listening on port 8080!

要停止应用程序,请在 Linux 和 Windows 用户中按CTRL+C或在 Mac 环境中按CMD+C

该应用程序现在运行在端口 8080. 它会听到 POST / HTTP 请求,并将在 /public 文件夹下的文件服务到 /. POST / 终端将返回一个字符串 Logging in...,而 GET / 将服务于您下次创建的登录 HTML 表格。

但是,如果您关闭当前会话到服务器时,该应用程序将停止运行. 即使在您退出服务器后,您还会使用 PM2,为 Node.js 应用程序提供一个流程管理器。

在本教程的前提下,您创建了一个运行PM2的Node.js应用程序。

使用 PM2 启动应用程序:

1pm2 start /srv/rate-limited-login/index.js --name rate-limited-login

使用--name旗帜,您可以给PM2进程一个标签(在这种情况下,限率登录)。

将新应用程序添加到您已保存的 PM2 应用程序列表中,这些应用程序将在启动后运行:

1pm2 save

要测试应用程序是否正在运行,请向其发送 POST 请求:

1curl -X POST http://localhost:8080/

如果一切都配置正确,输出应该看起来像这样:

1[secondary_label Output]
2Logging in...

<$>[注] ** 注意:** 请记住,真实世界的登录将有额外的安全性,这不是在本教程中考虑的。 您可以通过遵循 OWASP Authentication Cheat Sheet来了解更多有关身份验证安全指南。 第三方库如 Passport也可以用于身份验证目的,并且他们已经建立了安全机制来帮助防止恶意攻击。

现在你已经设置了应用程序,你已经准备好设置率限制器。

步骤二:设置利率限制

在此时, Nginx 将流量重定向到登录应用程序. 在此步骤中,您将使用三个 Nginx 指令实现率限制: limit_req_zone, limit_reqlimit_req_status

第一个,‘limit_req_zone’,指定了限制请求的标准,您给 Nginx 存储的内存量,以便跟踪以前请求的数据,以及在一段时间内限率。

速率限制器设置在您之前在本教程的前提部分创建的相同配置块中。

1sudo nano /etc/nginx/sites-available/login.your_domain.com

添加一个新的limit_req_zone Nginx 指令:

1[label /etc/nginx/sites-available/login.your_domain.com]
2...
3limit_req_zone $binary_remote_addr zone=login_limit:10m rate=5r/s
4server {
5...

上一行表示 Nginx 的请求应该基于 IP 地址以每秒 5 次请求率,并应该分配 10 兆字节以保持以前 IP 记录。

若要限制基于其他标准的请求(而不是使用客户端的 IP 地址作为密钥),您可以使用 $geoip_country_code 允许按国家进行请求。

您将使用的下一个指令是limit_req,它描述了该率限制应该应用的范围,以及是否有额外的设置,如爆炸延迟,可能对应用程序有益。

再次打开login.your_domain.com的区块配置文件:

1sudo nano /etc/nginx/sites-available/login.your_domain.com

通过采用limit_req Nginx 指令来定义应该执行利率限制的背景:

1[label /etc/nginx/sites-available/login.your_domain.com]
2...
3server {
4    ...
5    location / {
6    limit_req zone=login_limit;
7    ....
8}
9...

根据您的应用程序需求,limit_req 指令可以在 Nginx 配置块文件中的服务器位置环境中生存。

您将添加的最后一个指令是limit_req_status。 默认情况下,当用户触发率限制门槛时, Nginx 将提供一个503 Service Unavailable HTTP 代码。 该消息可能是误导性的,因为它可以给出一个印象,当服务器不像预期的那样工作时,事实上,它是客户端所提出的要求比 Nginx 设置允许的更多。

打开login.your_domain.com的区块配置文件:

1sudo nano /etc/nginx/sites-available/login.your_domain.com

通过添加 Nginx 指令来更改默认 HTTP 响应代码:limit_req_status:

1[label /etc/nginx/sites-available/login.your_domain.com]
2...
3limit_req_status 429
4server {
5...

在添加limit_req_zone,limit_reqlimit_req_status Nginx 指令到login.your_domain.com的区块配置文件后,您将有类似于以下内容的东西:

 1[label /etc/nginx/sites-available/login.your_domain.com]
 2limit_req_zone $binary_remote_addr zone=login_limit:10m rate=5r/s;
 3limit_req_status 429;
 4
 5server {
 6        root /var/www/login.your_domain.com/html;
 7        index index.html index.htm index.nginx-debian.html;
 8        server_name login.your_domain.com;
 9
10        location / {
11          limit_req zone=login_limit;
12          proxy_pass http://localhost:8080;
13          proxy_http_version 1.1;
14          proxy_set_header Upgrade $http_upgrade;
15          proxy_set_header Connection 'upgrade';
16          proxy_set_header Host $host;
17          proxy_cache_bypass $http_upgrade;
18        }
19
20    listen [::]:443 ssl ipv6only=on; # managed by Certbot
21    listen 443 ssl; # managed by Certbot
22    ssl_certificate /etc/letsencrypt/live/login.your_domain.com/fullchain.pem; # managed by Certbot
23    ssl_certificate_key /etc/letsencrypt/live/login.your_domain.com/privkey.pem; # managed by Certbot
24    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
25    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
26
27}
28
29server {
30    if ($host = login.your_domain.com) {
31        return 301 https://$host$request_uri;
32    } # managed by Certbot
33
34    listen 80;
35    listen [::]:80;
36    server_name login.your_domain.com;
37    return 404; # managed by Certbot
38
39}

最后,重新加载当前的 Nginx 配置:

1sudo service nginx reload

您可以通过访问login.your_domain.com来测试价格限制,并多次更新,直到您收到一个 429 错误页面。

步骤 3 – 使用 Apache Benchmark 验证率限制工作

浏览器只能按每页加载一个页面,这不适合测试并行连接并查看何时触发率限制。相反,您可以使用加载测试工具,如 k6Apache Benchmark. 在此步骤中,您将使用Apache Benchmark,因为其CLI不需要脚本代码来打击单个页面。

使用以下命令安装 Apache Benchmark:

1sudo apt install apache2-utils -y

要运行测试,启动100个同时连接和总计100个请求到您的登录:

1ab -c 100 -n 100 https://login.your_domain.com/

在 Apache Benchmark 完成测试后,它将显示许多数据点. 要评估率限制器是否按预期工作,请专注于以下行:

1[secondary_label Output]
2...
3Time taken for tests:   0.180 seconds 
4...
5Non-2xx responses:      99
6...

设置每秒 5 次请求率限制意味着每 200 毫米最多允许 1 次请求。

若要检查这些请求是否确实具有429状态的错误,请在另一个终端运行负载测试时显示 Nginx 的错误日志。

打开新终端并执行以下命令:

1[environment second]
2sudo tail -f /var/log/nginx/error.log

结果将看起来像这样:

1[secondary_label Output]
2[error] 17133#17133: *6346 limiting requests, excess: 0.540 by zone "login_limit", client: 203.0.113.0, server: login.your_domain.com, request: "GET / HTTP/1.0", host: "login.your_domain.com"

突出一些上述输出条目,‘过剩’代表了通过请求体现的率限制门槛后每毫秒的请求数量。

login_limit表示您在 Nginx 区块配置文件中指定的率限制组,如果您有两个或多个率限制器具有不同的设置,则将设置不同的区域来区分它们。

客户端告诉您访问者的IP地址,而主机请求分别代表了被访问的域和所提出的请求类型。

在接下来的步骤中,您可以保持这个终端窗口打开,以查看变更对率限制器的行为在配置变更中是如何影响的。

您现在可以确定率限制是按预期运作的,在下一步,您将使用额外的配置来调整 Nginx 以获得更现实的工作负载。

步骤 4 – 设置爆炸和延迟

如前所述,具有每秒5请求的速率限制意味着每200ms将允许一个请求,这可能不适用于一个应用程序,如网站,具有像图像,HTML,CSS和JS文件等资产,可能会更频繁地提出请求。

Nginx提供的解决方案之一是有一个爆炸排队,在返回错误之前会采取一些额外的请求。然而,依靠这个排队在短时间内允许多个请求可能会导致性能问题,因为请求排队进行处理,但不是同时。

还有一种使用延迟爆炸的混合方法,这允许你有一个爆炸排队,以便 Nginx 不会放弃请求,但只允许一小部分这些排队项目同时处理。

在此步骤中,您将探索所有这些方法,以便看到它们如何结合在一起,在设置率限制器时获得不同的行为。您将设置一个 Nginx 爆发队列,您还将使用更高级的配置,允许有限数量的请求通过率限制器而没有时间罚款。

您将首先设置一个爆发排队列。将爆发添加到limit_req指令中,可以让您定义需要排队的请求数量以进行后续处理。这样,您可以告诉 Nginx 接受最多 10 个额外的资产请求,而不是返回429错误。 默认情况下,图像、JS 文件和 CSS 文件将以率限制器规定的速度提供服务(在这种情况下,每项请求为 200ms)。

首先,打开您的网站的配置文件:

1sudo nano /etc/nginx/sites-available/login.your_domain.com

配置 Nginx 以排队最多 10 个请求:

1[label /etc/nginx/sites-available/login.your_domain.com]
2...
3limit_req zone=login_limit burst=10;
4...

更新 Nginx 配置:

1sudo service nginx reload

通过再次运行 ab 来测试率限制器的变化:

1ab -c 100 -n 100 https://login.your_domain.com/

您将看到以下结果:

1[secondary_label Output]
2...
3Time taken for tests:   2.004 seconds
4...
5Non-2xx responses:      89
6...

在每秒 5 个请求(或每分钟 300 个请求的等级)的速度限制下,10 个同时执行的 Web 资产请求需要 2 秒(每 200 毫米 1 秒),这会损害用户的体验。

然而,在limit_req中使用nodelay使 Nginx 能够在爆发排列中处理请求,而不会在每个请求之间等待 200ms。

要设置此配置,请打开您的 Nginx 块配置文件:

1sudo nano /etc/nginx/sites-available/login.your_domain.com

允许nodelay在爆发排列中服务请求,而不会在请求之间等待:

1[label /etc/nginx/sites-available/login.your_domain.com]
2...
3limit_req zone=login_limit burst=10 nodelay;
4...

使用此命令重新加载 Nginx:

1sudo service nginx reload

最后,检查爆炸排列中的排列请求不会延迟:

1ab -c 100 -n 100 https://login.your_domain.com/

结果将类似于以下:

1[secondary_label Output]
2...
3Time taken for tests:   0.233 seconds
4...
5Non-2xx responses:      89
6...

相同数量的10个请求不会被锁定,等待他们的轮到被服务,而用户之前不得不等待的2秒只会减少到网络和服务器处理所需的时间来交付它们。

如果同一个用户试图在 Nginx 清除爆发队列中的插槽之前获取第 11 个资产,请求将被拒绝。

使用nodelay增强了用户体验,因为同时请求被尽可能快地交付;另一方面,不使用nodelay提供了更受控的请求流向应用程序,因为请求之间实施了等待间隔。

从 Nginx 1.15.7 开始,延迟允许您将这两种方法结合起来,它允许您定义有多少请求可以绕过整个爆发排队的延迟。

要使用此方法,请先打开您的 Nginx 块配置文件:

1sudo nano /etc/nginx/sites-available/login.your_domain.com

nodelay替换为delay,允许最多处理4项请求:

1[label /etc/nginx/sites-available/login.your_domain.com]
2...
3limit_req zone=login_limit burst=10 delay=4;
4...

更新当前的 Nginx 配置:

1sudo service nginx reload

最后,检查延迟是否按预期工作:

1ab -c 100 -n 100 https://login.your_domain.com/

结果将看起来像这样:

1[secondary_label Output]
2...
3Time taken for tests:   1.205 seconds
4...
5Non-2xx responses:      89
6...

最多4个请求将能够避免请求之间的利率限制延迟,就像他们使用nodelay一样。

在爆炸队列中剩余的 6 个请求将与率限制器指定的等待时间一起被接受(例如,在 6 个请求之间 200ms)。

您现在可以快速交付第一个请求,同时避免过度使用并拒绝通过 Nginx 提供过多的流量. 为了进一步改善用户体验,您可以提供更好的 429 错误页面,这些页面更符合您的网站设计。

步骤 5 — 定制 429 错误页面(可选)

每当访问者超过您的应用程序允许的请求数时,他们都会遇到一个429错误响应,其中包括由 Nginx 提供的 HTML 页面。 为了改善您的应用程序的用户体验,您可能希望 429 错误 页面匹配正常页面格式。

创建一个文件夹429错误:

1sudo mkdir -p /usr/share/nginx/errors/429

创建一个新的429.html文件:

1sudo nano /usr/share/nginx/errors/429/429.html

当出现429错误时,定义要返回的内容:

 1[label /usr/share/nginx/errors/429/429.html]
 2<html>
 3
 4<head>
 5    <title>Error 429</title>
 6    <link href="https://fonts.googleapis.com/css2?family=Bangers&display=swap" rel="stylesheet">
 7    <link rel="stylesheet" href="/errors/429/429.css">
 8    <script></script>
 9</head>
10
11<body>
12    <div id="login-container">
13        <h1>Error 429 - Too many requests</h1>
14        <p>You have been rate limited.</p>
15    </div>
16</body>
17
18</html>

429 错误 页面添加一些 CSS 样式:

1sudo nano /usr/share/nginx/errors/429/429.css

您可以添加一些风格来定义梯度为背景,更改默认字体,中心文本,并允许身体占用浏览器高度的100%:

1[label /usr/share/nginx/errors/429/429.css]
2body {
3    background: linear-gradient(132deg, rgba(184,231,209,1) 35%, rgba(0,255,190,1) 100%);
4    font-family: Arial, Helvetica, sans-serif;
5    height: 100%;
6    overflow: hidden;
7    text-align: center;
8}

您还可以增加大小并更改标题的字体:

1[label /usr/share/nginx/errors/429/429.css]
2...
3h1 {
4    font-size: 2em;
5    font-family: 'Bangers', cursive;
6}
7...

现在你已经准备好在429错误中服务的文件,配置你的 Nginx 区块配置:

1sudo nano /etc/nginx/sites-available/login.your_domain.com

添加 Nginx 指令 error_page 来描述您先前创建的 HTML 文件路径,并在 /errors 下向公众提供 `/usr/share/nginx/errors。

1[label /etc/nginx/sites-available/login.your_domain.com]
2...
3server {
4        ...
5        error_page 429 /errors/429/429.html;
6        ...
7}
8...

重新加载 Nginx 以反映新变化:

1sudo service nginx reload

最後,請前往「login.your_domain.com」並更新它,直到您啟動率限制器並看到新的 429 錯誤頁面。

结论

在本文中,您配置并调整了 Nginx 以限制接入请求的速度,以防止过多的流量从互联网上访问它。

作为下一步,请考虑查看 Nginx 产品文档 限制访问代理 HTTP 资源,以了解如何限制每 IP 的带宽或连接数量。

有关安全的更多信息,请参阅DigitalOcean的安全教程(https://andsky.com/tags/security),Megan Snyder的关于安全部署的演讲(https://www.digitalocean.com/community/tech_talks/securing-your-deploy),以及Mason Egger的关于计算机安全的基础的演讲(https://www.digitalocean.com/community/tech_talks/foundations-of-computer-security)。

Published At
Categories with 技术
Tagged with
comments powered by Disqus