Token-based лімітування кількості з'єднань

Усі знають про ліміти кількості підключень з одного IP (IP-based), але що робити, якщо ми хочемо обмежити кількість підключень до деякого API на один токен авторизації?
І не важливо, скільки різних IP буде використано.

Частина конфігу nginx:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
map $request_uri $client_token {
    "~*(?i)(token=)([a-f0-9]{32})" $2;      # regex return <32str>
    default                        "";      # Fallback to limit_req_zone:global
}

limit_req_zone  $binary_remote_addr   zone=global:32m       rate=100r/s;    # Rule_1
limit_req_zone  $client_token         zone=tokenlimit:32m   rate=5r/s;      # Rule_2
limit_req       zone=global           burst=25;

server {
        location / {
            index index.html;
            root /var/www/html;
        }
        location = /api {
            index index.html;
            root /var/www/api/html;
            limit_req   zone=tokenlimit   burst=5 nodelay;  # api location
            limit_req   zone=global;                        # Fallback
            limit_req_status              429;              # 503

Приклад змішаного блокування

Імітуємо безліч підключень з хоста A без токена.

ab -n 999 -c 5 "http://api.tld/api?id=letmein"

Перевіряємо та отримуємо блокування IP A

Незалежно від наявності токена, він перевищив правило Rule_1.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
curl -v "http://api.tld/api?token=00000000000000000000000000000000"


< HTTP/1.1 429 Too Many Requests
< Server: nginx
< Date: Sun, 22 Jun 2024 20:03:37 GMT
< Content-Type: text/html
< Content-Length: 162
< Connection: keep-alive
< 
<html>
<head><title>429 Too Many Requests</title></head>
<body>
<center><h1>429 Too Many Requests</h1></center>
<hr><center>nginx</center>
</body>
</html>

IP B при цьому підключається без проблем

Він не перевищив ні кількість підключень з одного IP Rule_1, ні кількість підключень на один токен Rule_2.

1
2
3
4
5
6
7
8
9
10
curl -v "http://api.tld/api?token=00000000000000000000000000000000"


< HTTP/1.1 200 OK
< Server: nginx
< Date: Sun, 22 Jun 2024 20:04:18 GMT
< Content-Type: text/plain; charset=utf-8
< Content-Length: 801
< Connection: keep-alive
< X-App-TOKEN: 00000000000000000000000000000000

Приклад перевищення ліміту на один токен

Хост A

Імітуємо безліч підключень з хоста A використовуючи токен (111).

1
ab -n 999 -c 5 "http://api.tld/api?token=11111111111111111111111111111111"

Отримуємо блокування токена

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
curl -v "http://api.tld/api?token=11111111111111111111111111111111"


< HTTP/1.1 429 Too Many Requests
< Server: nginx
< Date: Sun, 22 Jun 2024 20:10:48 GMT
< Content-Type: text/html
< Content-Length: 162
< Connection: keep-alive
< 
<html>
<head><title>429 Too Many Requests</title></head>
<body>
<center><h1>429 Too Many Requests</h1></center>
<hr><center>nginx</center>
</body>
</html>

Хост B

Пробуємо використовувати цей же токен (111) з хоста B.

Отримуємо таке ж блокування токена

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
curl -v "http://api.tld/api?token=11111111111111111111111111111111"


< HTTP/1.1 429 Too Many Requests
< Server: nginx
< Date: Sun, 22 Jun 2024 20:12:42 GMT
< Content-Type: text/html
< Content-Length: 162
< Connection: keep-alive
< 
<html>
<head><title>429 Too Many Requests</title></head>
<body>
<center><h1>429 Too Many Requests</h1></center>
<hr><center>nginx</center>
</body>
</html>

Пробуємо використати інший токен (000) з хоста B.

Успішне підключення

1
2
3
4
5
6
7
8
9
10
curl -v "http://api.tld/api?token=00000000000000000000000000000000"


< HTTP/1.1 200 OK
< Server: nginx
< Date: Sun, 22 Jun 2024 20:09:07 GMT
< Content-Type: text/plain; charset=utf-8
< Content-Length: 805
< Connection: keep-alive
< X-App-TOKEN: 00000000000000000000000000000000

Таким чином, можна комбінувати різні ліміти, ґрунтуючись не тільки на IP, але й на параметрах запиту або location.