作者选择了 多样性在技术基金作为 写给捐款计划的一部分接受捐款。
介绍
速度限制器对于生产环境至关重要,因为它们有助于防止恶意粗力攻击访问敏感资源,这些资源通常被放置在网页登录后面,它们在阻止分布式拒绝服务(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_req
和 limit_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_req
和limit_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 验证率限制工作
浏览器只能按每页加载一个页面,这不适合测试并行连接并查看何时触发率限制。相反,您可以使用加载测试工具,如 k6或 Apache 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)。