Jedna od najvažnijih komponenti svakog API-ja, osim mogućnosti upravljanja podacima, je sigurnost. A jedna od osnovnih komponenti te sigurnosti je mogućnost blokiranja prekomjernih upita koji se šalju na pojedini API endpoint.
U ovom ću slučaju za tu svrhu koristiti Express Rate Limit modul.
Postavljanje projekta
Kreiram mapu naziva ExpressRateLimit i unutar nje pokrećem naredbu:
1 |
$ npm init --yes |
Odmah nakon toga instaliram Express.js i Express Rate Limit pakete:
1 2 |
$ npm install express --save $ npm install --save express-rate-limit |
Sada u mapi projekta mogu vidjeti datoteku package.json koja je osnova ovog projekta.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
{ "name": "ExpressRateLimit", "version": "1.0.0", "description": "ExpressJS Rate Limit API", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "ISC", "dependencies": { "express": "^4.16.3", "express-rate-limit": "^2.12.2" } } |
Sada imam sve potrebno za kreiranje API-ja.
Kreiranje API-ja
Kreiram index.js datoteku unutar koje kopiram sljedeći sadržaj:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
var express = require('express'); var app = express(); var RateLimit = require('express-rate-limit'); var port = process.env.PORT || 8080; var apiRoutes = express.Router(); apiRoutes.get('/', function(req, res) { //ako je sve ispravno postavljeno kao odgovor dobijem ovu poruku res.json({ message: 'API radi!' }); }); app.use('/api', apiRoutes); app.listen(port); console.log('API je pokrenut na portu:' + ' ' + port); |
API mogu testirati na putanji http://localhost:8080/api/
Sada, nakon što sam se uvjerio da API ispravno radi, mogu implementirati Express Rate Limit modul.
Express Rate Limit modul
Kreiram dvije krajnje točke (endpoint):
1 2 3 4 5 6 7 8 9 10 11 |
apiRoutes.get('/putanja1', function(req, res) { res.json({ success: true, message: 'Prvi API' }); }); apiRoutes.get('/putanja2', function(req, res) { res.json({ success: true, message: 'Drugi API' }); }); |
Njima pristupam putem URL-ova:
http://localhost:8080/api/putanja1 i http://localhost:8080/api/putanja2
Prije nego ubacim sigurnosnu zaštitu URL-ovima je moguće pristupiti neograničeno mnogo puta. Ovo nije problem kada, kao u ovom konkretnom slučaju, šaljem mali json objekt, ali kada je u pitanju API koji dohvaća više podataka iz npr. SQL baze onda to već postaje problem jer može doći do zagušenja ili potpunog blokiranja servera.
Sada ću dodati apiLimiter objekt s nekoliko parametara.
1 2 3 4 5 6 7 |
var apiLimiter= new RateLimit({ windowMs: 60*60*1000, // vremenski okvir od jednog sata unutar kojega vrijede ova pravila delayAfter: 1, // započeti usporavanje nakon prvog upita delayMs: 3*1000, // usporiti svaki sljedeći upit za 3 sekunde max: 5, // blokiranje API-ja nakon 5 upita message: "Previse zahtjeva s Vase IP, molimo pokusajte ponovno za jedan sat" }); |
Ako želim da se ta pravila primjene na sve krajnje točke (endpoint) dodajem sljedeće:
1 |
app.use('/api/', apiLimiter, apiRoutes); |
Mogu vidjeti da je svaki sljedeći upit sporiji od prethodnog i da se nakon 5 poslanih upita prikazuje poruka iz apiLimiter objekta.
U slučaju da za svaku krajnju točku tj. svaki endpoint želim postaviti drugačiji uvijet to radim tako da kreiram toliki broj objekata koliko ima krajnjih čvorova. Npr.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
var apiLimiter1 = new RateLimit({ windowMs: 60000, // vremenski okvir od jednog sata unutar kojega vrijede ova pravila delayAfter: 10, // započeti usporavanje nakon prvog upita delayMs: 1000, // usporiti svaki sljedeći upit za 3 sekunde max: 50, // blokiranje API-ja nakon 5 upita message: "Previse zahtjeva s Vase IP, molimo pokusajte ponovno kasnije" }); var apiLimiter2 = new RateLimit({ windowMs: 60*60*1000, // vremenski okvir od jednog sata unutar kojega vrijede ova pravila delayAfter: 1, // započeti usporavanje nakon prvog upita delayMs: 3*1000, // usporiti svaki sljedeći upit za 3 sekunde max: 5, // blokiranje API-ja nakon 5 upita message: "Previse zahtjeva s Vase IP, molimo pokusajte ponovno za jedan sat" }); apiRoutes.get('/', function(req, res, next) { //ako je sve ispravno postavljeno kao odgovor dobijem ovu poruku res.json({ message: 'API radi!' }); }); apiRoutes.get('/putanja1', apiLimiter1, function(req, res, next) { res.json({ success: true, message: 'Prvi API' }); }); apiRoutes.get('/putanja2', apiLimiter2, function(req, res, next) { res.json({ success: true, message: 'Drugi API' }); }); |