Абэрненне API на Express: аутентыкацыя, перакананне данных, обмежэння частоты запытоў і монітарынг.
Практычны ўвод у абэрненне API: аутентыкацыя за дапамогою Passport і JWT, моделі автарызацыі, шифраванне AES-GCM, перакананне данных, обмежэння частоты запытоў і логаванне.
Кожны API, які вы відкрываеце, ўособлівае дзверы да вашай системы, і хакеры пераглядаюць гэтыя дзверы набліжна болей систематычна, чым большасць команд іх тестуе. Безпека, якая дадзваецца пасля запуску, часта залишае прагалі: маршрут без захавання, запит, створанный на аднойчыных дадзенах, тэрмінал заходу, який без працы приймае мільйон спроб. У гэтым кяліку практычна інформацыя пра стандарты, якія варта знать, пра аб’язковыя загрозы і пра шасць конкрэтных слоёў захавання, якія реалізуюцца ў Node.js з Express, так што вы маглі б аудытаваць існуючы API або стварыць новы з захаванням ўжо з першага дня.
Вважайце тое, што післяйдзе, чарткай, да якой вяртаецеся працоўна ў всім цыкле развіцця: перад выпускам, пасля апдэйта і ўсё час, калі змінюецца якая-небудзь залежнасць або маршрут. Рэгулярныя перагляды — гэта спосаб з’яўлення вразломаў, калі яны ўсё ўсё маленькія.
Чаму API заслуговуюць на спецыяльную увагу да безпекі
Савэчасныя прылады все чыраз большая частка якіх складаецца з API. Узамест таго, каб стварыць усі функцыяны ў самай кампаніи, команды інтэгруюць службы платаў, аідэнтыфікацыі, перадачы паведамленняў і обробкі даных чераз чытка апісаныя інтерфейсы, і ўжо багато компаній выпускае прылады, якія ў першую чаргу апоўненыя API. Адна з даследжэнняў рынку ацэнівае размер эканамікі API на адпаведна 20 мільярда долераў да 2026 года (падсумак даследжэння); незалежна ад точных цифр, залежнасць рэальная і продовжвае растаць.
Гэтая залежнасць існуе ў двух напрамках. API даёт законным кліентам готовыя, можна перадаць у іншыя прылады функцыяны, а таксама даёт хакерам документаваны, прыемны для машын пункт выхіду. Даследжэнняя промыслу адносна да большай часткі нарадзек узвязоўваюцыя іх з API; даследжэння Traceable за 2023 год прызначае ім 74% усіх нарадзек даных.
API-я частаюць знаходзіцца прымо перед самым чутлівым дакументацыяй, якую мае компанія: платформамі ідэнтыфікаціі з персональнымі данымі, фінансавымі записамі, внутрашняяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяя
- OWASP API Security Top 10: рангаваная спіска самых критычных рызыкаў для API, якую падтрымвае Open Web Application Security Project. У ёй расследжуюцься такія проблемы, як несправны адзначэньні прав на роўні об’ектаў, падлажванне запытоў з боку сервера і неакранаваная спрацоўка рэсурсаў; гэта лепшая восьбовая пункт для анаізу загроз.
- OAuth 2.0 і 2.1: кантэкст адзначэньня прав па даверэнню. Кліент атрымляе токен даступу з вяліканымі межамі і выкарыстоўвае яго, каб дзейсніць дзеянні від імені пользователя, не працюючы з яго паролем; токены абнавлення дазволяюць кліенту атрымляць новыя токены даступу без падтрымкі пользователя знову.
- OpenID Connect (OIDC): шар ідэнтыфікацыі, які працуе над OAuth. Ён стандартызуе токен ідэнтыфікацыі і спосаб, яким кліенты яго паўтараюць, што дазволяе здзейсніць ўвод па адной пароль і загледзецца у прафіль у падрыхтавальніках ідэнтыфікацыі, забезпечваючы взаімадзейнаемасць.
Гэтыя элементы ствараюць моцную базу, аднак яны не ўтвараюць вялікога спісу.
Аб’екты атак, проты якіх вы захоўваецеся
Уразлівасці, якія найчышэй з’яўляюцца ў рэальных API, ёсьць:
- Несправна аутантыкацыя: слабыя або вялікія перакананні ў тыяміце і паслабленая обработка сэсыйяў дазволяюць зловерху красты кукізы або токены і перадаць іх знову, каб дася да вашых служб.
- Несправная автарызацыя на рэвэле об’ектаў (BOLA): API пераканваецца, што корыстнік заўважаны, але не пераканваецца, чы розрахована ўзаемнасупрацоўка з конкрэтным записам, таму змена ID у URL адкрывае доступ да дадзэнняў іншай адзобы чы внутрашніх процэсаў.
- Штырк SQL: данні, кантролюемыя зловерхам, прыўязваюцца да запиту і выкананы базай дадзэнняў. Практычна кожны кліент базай дадзэнняў адказвае механізам параметраў, які безпечна перадае значэнні.
- Штырк каманд: недазволеныя данні з запыту падаюць да системнага шэлу чы працоўніка каманд, дазволяючы зловерху выканаць будзь-якія каманды пад прывілеямі процэса вашаг сервера.
Кожны з гэтых аспектаў мае сваю практыку программавання. Рэшта гіду розглядае іх у шасці слоях, выкорыстоўваючы JavaScript і Express на всю гэтую час.
1. Аутентыкацыя: падтверджэнне таго, хто ўсілец
Кожны захаваны API должен вымагаць, каб кліент падтвердзіў свою ідэнтычнасць пры выкананні будзь-якіх значамых дзеянняў, чы то через імэнь паўтарэння та пароль, ключ API чы падпісаны токен. Методы аутентыкацыі зазвычай дзеляцца на пяць каляў: аутентыкацыя за імэнем паўтарэння та паролем, багатыфакторная аутентыкацыя, аутентыкацыя на базе токенаў, аутентыкацыя на базе сертыфікатаў і біаметрыя.
Шырока прычына плутання — гэта, што на самай працо заменяе JWT. Традыцыйныя серверскія сесіі зберагалі стан на сервере і выкарыстоўвалі кукі браузера для перадачы ідэнтыфікатора сесіі. JWT пазбавляе неабходнасці такога пошуку на сервере з кожным запитам, але сама по сабе не пераканальвае достовернасць дакументаў: яшчэ трэба пераканацца ў пароле прынеймна раз перш чым выдаваць токен. Самэльга таму хыбрыдны падход ўсё часта ўжываецца. Пользователь заходзіць праз электронную пошту і пароль, сервер выдае JWT у разы успеху, а кожны наступны запит прыносіць толькі токен. Перакананне у достовернасці дакументаў і аутентыкацыя з кожным запитам стаюць адначасна разнымі задачамі, і пароль больш не перадаецца з кожным вызывам.
У Express бібліятэка Passport падтрымляе оба аспекты за дапамой праграмнаў, якія можна падключыць. Настройка выконваецца за тры крокі.
Крок 1: зарэўнаваць локальную стратэгію і стратэгію JWT
Локальная стратэгія выконваецца адзін раз пад час заходу і яя адпавядае за пошук пользователя па электроннай паштцы і порэванаванне адправленага пароля з захоўваным хэшам. Стратэгія JWT выконваецца для кожнага захіщанага запиту: яна выявляе токен з заголовка Authorization: Bearer, пераканальвае падпіс за дапамою JWT_SECRET і выявляе пользователя, упамінанага ў пэйлоадзе. Экспорт упрымкі authenticateJWT, якая ўжо налаштавана, з параметрам session: false, чыніць небутаўны характер сэрвісу яшчэ болей выразным.
const passport = require("passport");
const LocalStrategy = require("passport-local").Strategy;
const { Strategy: JwtStrategy, ExtractJwt } = require("passport-jwt");
// Local Strategy: Verify username and password during login.
passport.use(
new LocalStrategy(
{ usernameField: "email", passwordField: "password" },
async (email, password, done) => {
// Find the user and compare the hashed password.
// If valid, return the user.
}
)
);
// JWT Strategy: Verify the token on protected requests.
passport.use(
new JwtStrategy(
{
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: process.env.JWT_SECRET,
},
async (payload, done) => {
// Find the user referenced in the token.
}
)
);
// Middleware
const authenticateJWT = passport.authenticate("jwt", { session: false, });
module.exports = { passport, authenticateJWT, };
Функцыі-вярненні пасля пераканальвання залежыць як каментары, і самэ тут адбываецца справжня работа з безпекай. Для порэванавання пароў должна быць викорыстоўвана медленная алгорытмія хэшавання з салтом, такая як bcrypt або Argon2, і трэба вяртаць однаковы загальны результат абякання ў тым случае, калі электронная пашта невядомая або пароль некорэктны, каб канцэнтры не моглі быць викорыстаныя для выявлення тых счотаў, якія існуюць.
Шаг 2: аутентыфікацыя пад заходам і падпісванне токена
Функцыя для заходу вызывае местную стратэгію через спецыяльны калебек. Адмоўкі перадаюцца системе адрабаткі адмоўкаў Express, відсутны корыстнік выдае код 401, а успешная атрыбуцыя дае токен, падпісаны ID і роллю корыстніка, а таксама тэрміном дзейнасці, які бераеца з JWT_EXPIRES_IN; у разе проблемы выкарыстоўваецца тэрмін у два гадзіны.
const jwt = require("jsonwebtoken");
const passport = require("passport");
const login = (req, res, next) => {
passport.authenticate("local", { session: false }, (err, user, info) => {
if (err) return next(err);
if (!user) return res.status(401).json({ message: info.message, });
// Issue a signed JWT after successful authentication.
const token = jwt.sign(
{ id: user.id, role: user.role,},
process.env.JWT_SECRET,
{ expiresIn: process.env.JWT_EXPIRES_IN || "2h",}
);
return res.status(200).json({ message: "Login successful.", token, user,});
})(req, res, next);
};
Два моменты заслуговуюць на увагу. Короткі термін дзейнасці вплывае на час, пры якім крадзены токен застаецца корыстным; якщо сесіі павінны трываць дыяўольш, неабходна супараванаваць короткія токены доступу з процэсам апавержэння, як описана ў нашай стратэгіі апавержэння токену для систем автанацыі Node.js. Кроме таго, у адпаведзі вяртаецца user у ўсіх деталях. Якщо гэты об’ект — це сырая рэкорд з базы дадзеных, ён можа мітць хэш пароля і внутрашнія поля, што якраз і є надмерным адкрыцьцем дакументацыі, пра якое гаворылася раней. У такім случае трэба вяртаць толькі неабходныя даны, такія як ID, адрыс электронной пошты і ролі.
Шаг 3: захаванне захіщанных маршрутаў
Пасля экспорту мідлвэра захаванне маршрута абычна выражаецца у размешчэнні функцыі authenticateJWT прымусова перед контролерам у ваказанні маршрута. Запыткі без чынныяго токена адхоўваюцца до таго, калі будзе запрацавана якая-небудзь бізнес-логіка.
const { Router } = require("express");
const authRouter = Router();
// Get auth middleware and sample prorected controller
const authController = require("../controllers/auth.controller");
const { authenticateJWT } = require("../middleware/authentication");
// Use JWT as a guard to protect certain routes
authRouter.get("/me", authenticateJWT, authController.me);
authRouter.patch("/password", authenticateJWT, authController.updatePassword);
Якщо вы вярбуецеся лёгкімі рутэрамі, той самы захоўнік можа быць уключаны ў сябе ў ланцуг мідлвэра контролера. У будзьму разе нехай захаванне будзе стандартам для рутэра, а публічныя маршруты трэба спецыяльна адзначыць як выключаныя, замест таго каб памяцаць прыкладзваць захоўнік кожны маршрут окрема.
2. Автарызацыя: выбір таго, што можа рабіць вызывач
Аутентыкацыя адпавядае на пытанне "хто вы?"; автарызацыя — на пытанне "што вам дазволена робіць?". Яна зазвычай выконваліцца ведаў за аутентыкацыяй і пераканаліць кожную ідэнтычнасць па правілах доступу пры тым, калі надаць або адмовіць у запытке. Без яе будзь-яны залогаваны корыстнік можа чытаць чутлівыя даны або запускать прывілейованыя дзеянні, і самэ так і выступаюць уразлівасці BOLA.
Тры модэлі падпіраюць большасць задач:
- Контроль даступу на адміністратыўной базе (RBAC) прызначае правыя на ролі, а ролі — на корыстувальнікаў. API для блога можа мець ролі адміністратара, рэдагувальніка і пераглядача. Ён падходзіць для стабільных функцый і груп, таму ён часта викорыстоўваецца ў корпаратывных працоёмках.
- Контроль даступу на атрыбутнай базе (ABAC) ацэнюе атрыбуты корыстувальніка, ресурса і сяродавішча запыту (віддзел, чутлівасць ресурса, час дня, сеть) па правілах. Ён падходзіць для API, чыяя рашэнні ў значным ступені залежнаць ад контексту або часта зміняюцца.
- Контроль даступу на асоцыяцыйной базе (ReBAC) надае даступ на аднойчынку з асоціяцыяй межа корыстувальнікам і конкрэтным ресурсам, такімі як прыналежнасць да власніка або членства ў групе, як правило, перакантрольваючы граф асоціяцый. Ён ідеальна падходзіць для саўместных продуктов, такіх як адналежнасць дакументаў або соцыяльных платформ.
Самастаяцэнне разрешэнняў на стварэнне є адличны спосаб разумець ўсія ўскладненняя, але системы вырабоцтва часта перадаюць задачу выдачы та паверкі токэнаў апрэтніку ідэнтычнасці. Калі API рэгіструецца у такога апрэтніка, як Microsoft Entra ID, і налашоўваецца так, каб прымалі токэны-носікі, чулівыя канцэнтры паверчаюць дыапазоны та ролі кожнага токэна пры выкананні. Некоректны токэн або відсутняе права прыводзяць да коду 401 Unauthorized. У Express захіщаны маршрут выглядае так:
app.get(
"/api/orders",
passport.authenticate("oauth-bearer", { session: false }),
(req, res) => {
res.json({ message: "Protected resource." });
}
);
Памятайце, што паверканы токэн стварае лишо загальныя права. Перакананні на рэвэле об’ектаў, такія як «Чы гэты замовленне належыць гэтаму корыстніку?», все рава трэба выканаваць у вашам хэндлеры або шаре дадзеных, таму што жадны апрэтнік ідэнтычнасці не ведае, хто ўладае рэядком 4812 у вашай базе дадзеных.
Для самых пратактаваў можна спынуцца на стандартах промышленасці, такіх як OAuth 2.0, OpenID Connect і SAML. Вы можете самі рэалізаваць ціяўныя процесы або перадаць іх прадаўцам служб аутэнтыкацыі, такім как Ping Identity, Okta, Microsoft Entra ID, AWS чыста IBM Security Verify.
3. Шифраванне: захаванне дадзеных па час перадачы і калі яны збераглі
Шифраванне ператварае чытальныя дадзеныя на зашифраваны тэкст, які ўтрачае значэнне без правильнага кляча. TLS захоўвае дадзеныя па час ўсунення; шифраванне дадзеных у стацыонарным стане захоўвае іх там, дзе яны зберагліся, укладаючы ў тое жа базу дадзеных. Чутлівыя системы зазвычай вядуць ужоўвае шифраванне, таму што без яго дадзеныя, такіе як фінансавыя кранцыпты, могу быць перехопленыя чыста выкрадзеныя з атакаванага сховішча.
Галоўныя падходы выміняюць швальнасць на кантроль над клячамі:
- Сіметрычная крыптаграфія выкарыстоўвае адну спаканую ключ. Яна дужа швайная і лепа справляецца з вялікімі об’ёмамі дадзеных, таму яе можна выбраць для зашыфравання дадзеных, якія знаходзяцца у спакоўнам стане, і пэйлоадаў, але обе стороны должны безпечна прытрымвацца таго ж секретнага ключа.
- Асіметрычная крыптаграфія выкарыстоўвае пару публічнага і прыватнага ключоў, таму не трэба адмахвачыць спаканую інфармацыю. Яна значна медленнейшая і практычная толькі для невяліких фрагментаў дадзеных.
- Гібрыдная крыптаграфія спаўнае гэтыя два падходы: асіметрычная крыптаграфія заходзіць сіметрычны ключ, а сіметрычны крыліч заходзіць большую частку дадзеных. Так можна апытацца як перавагамі адмахвання ключоў з першага падходу, так і швайнасцю другога.
Для сіметрычнага шифрування вбудованный у Node модуль crypto падрэжае AES-256-GCM. Наведзены выкладком працоўнік серыялізуе тэла запиту, стварае новы вектор ініцыялізацыі дужынай 12 байт, шифруе даныя і вяртае IV, таг аутентыкацыі GCM і зашифраваны текст у виглядзе шэсціканальных строкаў.
const crypto = require("crypto");
const algorithm = "aes-256-gcm";
const key = Buffer.from(process.env.ENCRYPTION_KEY, "hex");
app.post("/api/orders", (req, res) => {
const iv = crypto.randomBytes(12);
const cipher = crypto.createCipheriv(algorithm, key, iv);
const encrypted = Buffer.concat([
cipher.update(JSON.stringify(req.body), "utf8"),
cipher.final(),
]);
const payload = {
iv: iv.toString("hex"),
tag: cipher.getAuthTag().toString("hex"),
data: encrypted.toString("hex"),
};
// Store or transmit the encrypted payload
res.json(payload);
});
Калькольныя прычыны робяць гэта правільным. Ключ павінен мець роўна 32 байты (64 хэкс-знакі ў ENCRYPTION_KEY) і яго трэба браць з менеджера секрэтных дадзеных, а не з коду выканання. IV павінен быць унікальным для кожнай архівацыі пад тым самым ключам; павторны выкарыстоўкі IV з GCM мае катастрофічныя наследкі, таму ён генеруецца для кожнага запиту. Атрыбут аутентыкацыі дапамагае стороне, якая дэкріптуе, выявіць парадукты, таму яго трэба зберагаць разам з зашифраваным тэкстом і пераканацца ў яго наявнасці пад час дэкріптавання. У рэальным сервісе гэты пакет дадзеных можна бы б зберагчыць адноўна або перадаць далей, а не вяртаць вызывачу, як гэта робіцца ў дэманстрацыі.
У там самым модуле таксама є можлівасць асиметрычнай архівацыі. Дадзеныя, зашифраваныя публічным ключам, можна дэкріптуваць толькі за дапамогою адпаведнага прыватнага ключа:
const crypto = require("crypto");
const encrypted = crypto.publicEncrypt(
publicKey,
Buffer.from("Sensitive API data")
);
Пакалькі RSA можа шифруваць толькі пакет дадзеных, які маюць размах меншы за размах яе кляча, таму publicEncrypt падходзіць для короткіх значэнняў, такіх як поля з секретнымі дадзенымі або сімэтрычная кляча, а не для цэлых дакументаў. Гэтыя абмежэння ўсунуліся за дапамогою гібрыднага падходу: стварыцца тымчасовая кляча AES, пакет дадзеных шифруецца яю, а кляча AES — шыфруецца публічной клячай RSA абонента, і яны адправляюцца разам. Абонент выкарыстоўвае сваю прыватную клячу, каб адзysці клячу AES, а пасля дэшыфруець пакет дадзеных. Большасць API ніколі не будуць патрабаваць гэтага ў коде прыемліка, адколькі TLS вже выканае падобную замену, але гэта важна для розумэння сцэнарыяў кінца-кінца шыфравання.
4. Адзначэнне правамоўнасці і чыстка вхідных дадзеных
Калі ваш API прыме даныя кліента, вы не можете спакаваць, што самэ гэта будзе. Неправільна структура тэла, фрагменты SQL і навантажэння скрыпта выглядаюць як звычныя строкі, пакуль калі-небудзь іх не будзе адгукавацца. Две взаімадопамагальныя тэхнікі рашуюць гэтыя проблемы. Паўеракцыя адхоўляе вхідныя даны, якія нарушаюць вашы структурныя і сэмантычныя правілы. Санітараванне ператворае прынятыя даныя у безпечную, нормалізаваную форму прытаманне вашым обробнікам.
Спачатку застосавайце тип кантэнту
Найякшы спосаб працэўнікавання — глядзець на формат самай запыткі. Гэты невялікі мідлвэрфактар выкарыстоўвае req.is(), каб парабачыць Content-Type, і ў працоўнікаванне выдае 415 Unsupported Media Type, якщо гэта не так. Яго можна задаць на глобальным, роутэр-залежным або канкрэтнага эндпункта рэвэру.
const requireContentType = (type) => (req, res, next) => {
if (!req.is(type)) {
return res.status(415).json({ error: "Unsupported Media Type", });
}
next();
};
app.post("/api/users", requireContentType("application/json"),
(req, res) => {
res.json({ message: "User created." });
}
);
Паўеракцыя формы і значэння тэла
Пасля застосавання пакета формата, наступны слой пераконтроўвае, чыя запит ў правільной форме. За дапамогай express-validator правілы трэба зберагчыць у спецыяльным модулі-валідатора. Ён выкарыстоўвае синтаксычна правільную адресу электронной пошты, запускае асінхронную спецыяльную пераконтроўку, якая адхіляе адресы, якія вже ў базе дадзенаў, і прыменяе правілу прынеймна восьмі симвалаў у пароле.
const { body } = require("express-validator");
const { getUserEmail } = require("../db/queries");
const validateRegistration = [
body("email")
.isEmail()
.withMessage("Invalid email format")
.custom(async (value) => {
if (await getUserEmail(value)) {
throw new Error("Email is already in use");
}
return true;
}),
body("password")
.isLength({ min: 8 })
.withMessage("Password must be at least 8 characters long"),
];
module.exports = { validateRegistration }
Потым масэва валідатораў ставяцца ў ланцуг мідлвэра маршрута. Унутры обрабоўчыка validationResult(req) скупляе ўсія памылкі, і маршрут вяртае код 400 з усім списакам, а не продаўжае роботу.
const { validationResult } = require("express-validator");
const { validateRegistration } = require("../validators/userValidator");
app.post("/api/register", validateRegistration, (req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
res.json({ message: "Registration successful." });
});
Очышчэнне пасля валідацыі
Паколькі Express обрабоўвае мідлвэры па порядку, ланцуг очышчэння можа знаходзіцца адразу пасля валідацыі. Ёсць тут адчысцэнне і HTML-екрапсаванне імені, а таксама нормалізацыя адресы электронной пошты.
const sanitizeRegistration = [
body("firstName").trim().escape(),
body("email").normalizeEmail(),
];
app.post(
"/api/register",
validateRegistration,
sanitizeRegistration,
(req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
res.json({ message: "Registration successful." });
}
);
У гэтым случае порядак мае значэнне. Перакантроль на унікальнасць у валідаторы працюе раней за normalizeEmail(), таму два варыянты напісання адной і той жа адресы могу працягнуцца без перакантролі і стварыць дублікаты абанкаў. Нормалізацыя да ўтрымкання раней за пошук, або прыменэнне правіла унікальнасць да нормалізаванага значэння на рэвэле базы дадзеных, закрывае гэтыя праслейкі. Таксама будзьце абераглівы ў викорыстоўванні escape(): HTML-кодаванне вхідных дадзеных захоўвае шаблоны, якія ўтвараюць адпаведны выхід, але гэта зменяе скарыстаныя дадзеныя, і багато команд воліяць скарыстацца некодаванымі значэннямі і толькі кодаваць іх пад час выхіднай обробкі. Якщо вы розглядаеце бібліятэкі для валідацыі, наша параўнанне Zod і express-validator паказвае ўсі перавагі і недзеянні кожной з яных.
Для базы дадзеных вярцайце параметрызаваныя запыты
Ніколы не ствараце SQL-запыткі паляганням на скалькованні даных, які аднаёсць вядомыя. Кліенты базаў дадзеных, такія як pg, і ORM-системы, такія як Prisma, падтрымляюць параметрызаваныя запыткі, якія адправляюць текст запыткі і значэнні окалічна, таму база дадзеных завжды спрацоўвае з вхіднымі данымі як з дадзенням, а не як з выконвальным SQL.
У разе pg стварыце пул з’яўленняў аднойчы і экспортуйце яго для вашых модуляў дадзеных:
const { Pool } = require("pg");
const pool = new Pool({
connectionString: process.env.DATABASE_URL,
});
module.exports = pool;
Запыткі пасля чаго выкарыстоўваюць нумераваныя месца падставы ($1, $2) з значэннямі, якія аддаюцца як окалічны масэвы. Нават якщо email содержыць цітатку, а пасля яе — DROP TABLE, гэта будзе зберагвацца як літэральны строк.
app.post("/api/users", async (req, res) => {
const { email, name } = req.body;
await pool.query(
"INSERT INTO users (email, name) VALUES ($1, $2)",
[email, name]
);
res.status(201).json({ message: "User created." });
});
Разам гэтыя элементы ствараюць шаравую схему обробкі. Express-validator дазволяе ізаліраваць правіла як перадпрацоўваныя елементы, якія працуюць як мідлвэры і фіксуюць кожную неудачу, тады як параметрызаванне значыць, што нават вхідныя даны, якія працягнуліся праз верыфікацыю, не можаў перапісаць вашы запыткі.
5. Абсалютна та пропорцыянальная лімітацыя
Абсалютная лімітацыя встановляе максымальную колькасць запитоў, якія кліент можа адправіць за певны перыяд часу. Яна стрымвае спробы атакі сілой та атакі на відмову у службе, а таксама не дазволяе аднаму інтэнсыўнаму корыстніку перасланцаваць ресурсы для іншых.
Ліміты можна застаўляць па разных критэрыях:
- Па кліенту: запиты лічуюцца па кожнам ключы API або IP-адрэсе. Калі кліент досягае максымальнага ліміту, ён чакае, пакуль перыяд зноў не запачнёцца, або адскарыгвае вышэйшы ліміт, часта на платнай версіі.
- Па геаграфіі чы роце: ліміты разніцаюцца залежна ад регіону чы перыяду часу; напрыклад, дазваляе больш трафіку з регіонаў, дзе працуюць вашы кліянты, і збільшвае ліміты там, дзе выходзіць падозрэльны трафік.
Існуе множнацьце алгорытмаў (фіксаванае вікно, катячаеся вікно, контейнер токенаў), і вам не трэба самім іх реалізаваць, каб пачаць. Мідлвэр express-rate-limit за замовчаннем пісае колькасць запытоў за IP-адрэсу. У прыкладзе нижэй задаецца загальны ліміт у 100 запытоў за 15 хвілін для всьго, што знаходзіцца пад /api, а таксама значна строгія меры — пяць спроб за пяць хвілін для аблогавання, пры чым для адхоўных запытоѳ задаецца індывідуальная паведамленне.
const rateLimit = require("express-rate-limit");
// Apply to all API routes
const apiLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100,
});
// Apply stricter limits to authentication endpoints
const loginLimiter = rateLimit({
windowMs: 5 * 60 * 1000, // 5 minutes
max: 5,
message: "Too many login attempts. Please try again later.",
});
app.use("/api", apiLimiter);
app.post("/api/login", loginLimiter, (req, res) => {
res.json({ message: "Login successful." });
});
Публічныя API зазвычай выдаюць кожнаму корыстніку ключ, і обмежэнне па гэтым ключы є справядлівейшым, чым обмежэнне па IP-адресе, адколькі багато корыстнікаў можа выкарыстоваць аднойчы той самы адрес за дапамогою корпаратыўнага проксі. Спецыяльны keyGenerator чытае заголовак X-API-Key і выкарыстоўвае яго як ідэнтыфікатор лічыльніка.
const rateLimit = require("express-rate-limit");
const apiKeyLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 1000,
keyGenerator: (req) => req.get("X-API-Key"),
});
app.use("/api", apiKeyLimiter);
У такой форме кожны запит, які не паводзіць заголовак, стварае той самы ключ undefined і выкарыстоўвае аднойчы той самы бакет. У практыцы трэба адхіліваць запиты без ключа раней у ланцузе обробкі або перайсці на варыянт з выкарыстоўванням IP-адресы.
Обмежэнне частоты запытоў да дорогіх канцэнтраў
Регуляванне частоты прыема запыткаў кантролюе, як шыбкая ўзростае колькасць запыткаў, каб не прыводзіла да перавантажэння службы. У наступной конфігурацыі викорыстоўваецца той самы мідлвэр з вельмі кораткімі інтерваламі: максымальна дзесяць запыткаў за секунду па всім API, і толькі адзін запытак на пошук кожныя две секунды на кліента, таму што пошук ёсць найбольш ресурсаў-затратным канцэнтрам.
const rateLimit = require("express-rate-limit");
// Throttle all API requests
const apiThrottle = rateLimit({
windowMs: 1000, // 1 second
max: 10, // Allow up to 10 requests per second
});
// Apply a stricter throttle to resource-intensive endpoints
const searchThrottle = rateLimit({
windowMs: 2000, // 2 seconds
max: 1, // Allow 1 request every 2 seconds
message: "Please wait before sending another search request.",
});
app.use("/api", apiThrottle);
app.get("/api/search", searchThrottle, (req, res) => {
res.json({ results: [] });
});
Строга кажучы, гэта яшчэ ўсё ўскладненне частоты запытоў з маленькімі інтерваламі: занадто большыя запыты адхоўляюцца з кодам 429, а не з затрымкай. Якщо вы хочаце справжню регуляцыю частоты запытоў, якая спаможа сповільніць кліенты пры ўхіле запыту, то пакеты на кшталт express-slow-down дадаюць поступовыя затрымкі. Таксама зважайце, што стандартны внутршній база дадзеных лічыць запыты па процэсу, таму за лоад-балансерам з кальколькома інстанцямі патрэбна спакульная база дадзеных, такая як Redis, каб ліміты застаўаліся. Няўзабавныя выданні express-rate-limit таксама называюць опцыю max як limit; пераканайцеся ў даследжэннях да версіі, яку вы інсталюеце. Для лёгкай альтэрнатывы на TypeScript, пагледзіце нашу статыю пра мінімальны лімітар частоты запытоў для Express.
6. Логаванне, монітарынг і выяўлення інцыдэтаў
Вы не можете адактувацься на атаку, якой ніколі не бачылі. Логаванне фіксуе запыты і адказы разам з ўсімі метаданнымі, контекстам, часамы і кодамі адказаў, тады вы можете раскрыць прычыны проблем, адбавіць аудыт і зрозумець рэальную стаўку викорыстоўвання. Монітараванне стежыць за актывасцю у рэальны час, фіксуе такія паказнікі, як затрымка, частка адказаў з адлучэннямі і працэсная спроможнасць, а таксама выяўляе анамаліі, якія можуць свідчыць пра зловжывання, недасягненне цэляў на роўні абслугоўвання чыя-небудзь вразлівасці.
Галоўныя падходы і ўсе яныя перакананні:
- Логаванне запытоў за дапамогою мідлвэра, такога як Morgan, дышчыльна фіксуе кожны прыходзячы HTTP-запыт, але не дае інфармацыі пра стан системы.
Будзь-канэ з іх падключаецца да Express. Далей прыводзяцца чатыры популярныя базовыя елементы.
Логаванне запитоў з Morgan
Рэгістрацыя Morgan у формате combined запісвае лінію у стылі Apache для кожнага запиту, ўкладаючы метод, шлях, статус, размер адпаведзі і user agent.
const express = require("express");
const morgan = require("morgan");
const app = express();
// Log every incoming request
app.use(morgan("combined"));
app.get("/api/users", (req, res) => {
res.json({ message: "Users retrieved successfully." });
});
Структуравана логаванне прыкладнення з Winston
Winston фіксуе запускай у вачынку структураваных об’ектаў. Логаванне ID пользователя і замовлення пад час стварэння замовлення стварае летак аудыту, які можна пазнійша пераследаваць.
const winston = require("winston");
const logger = winston.createLogger({
transports: [
new winston.transports.Console(),
],
});
app.post("/api/orders", (req, res) => {
logger.info("Order created", {
userId: req.user.id,
orderId: req.body.id,
});
res.status(201).json({ message: "Order created." });
});
Будзьце абераглівы ў тым, што падаецца у логі. ID пользователяя ў порядку; паролі, токены, цэлыя номеры картак і весь тэкст запитоў — ні, а логі ёсць распашчаным месца для вытэкання чутлівых дадзеных.
Цэнтрызавана логаванне адказоў на бяды
Мідлвэр для шырокага адзьявлення памылак, які можна распазнаваць па чатырох аргументах, ловіць памылкі з любога маршрута. Ён фіксуее прычыну памылкі разам з шляхам і методам і вяртае стандартны код 500, тады кантэкст памылкі і внутранія дакладнасці ніколі не доходзяць да кліента.
app.use((err, req, res, next) => {
/* Logger is built as an independent module or class */
logger.error(err.message, {
path: req.originalUrl,
method: req.method,
});
res.status(500).json({
error: "Internal Server Error",
});
});
Адкрыць метрыкі для Prometheus
Бібліятэка prom-client саўеруе стандартныя метрыкі процэса Node.js і адкрывае іх на канцэнтры /metrics, каб Prometheus могаў іх збіраць.
const client = require("prom-client");
client.collectDefaultMetrics();
app.get("/metrics", async (req, res) => {
res.set("Content-Type", client.register.contentType);
res.end(await client.register.metrics());
});
Этот канцэнтр адкрывае внутранія дакладнасці пра вашу службу, таму яго трэба адграніцаваць да сеткі монітарынгу або захаваць праз аутэнтыфікацыю, а не залець яго доступным для всіх.
Фрэймворкі з чыстаю структураю дапамагаюць у гэтым случыку. NestJS выклікаецца з вбудованым логгерам і структурой, якая спрыяе прыўязцы логгавання і метрык да кожнага эндпункта, тады як у фрэймворку без чыстай структураю, такім як Express, патрэбна ўсвядомлена дадача логгавання, зазвычай у вастоўцы-мідлвэры, якая запускаецца перад адправкай адпаведзі. Адкрыць нешта новае ствараецца на аснове гэтых логаў: паведамленні пра маршруты з падозрэльнымі патернамі, такімі як растышча ў адпаведзях 401 чыста ў канал паведамленняў, які фактычна стежыце ваша команда.
Безпека як непаўзлівы процес
Няма жаднай статты, якая б пакрыла все, але пры кожнай выпуску можна пераканацца, што ваш API адпавядае гэтам базовым трэбаванням:
- Кожны эндпункт аддаёцца толькі через HTTPS.
- Існуе OAuth чыста адпаведны механізм токенаў.
- Выданые JWT-ы маюць тэрмін выконання.
- Ліміты заходзяць усе маршруты, прычаму на входзе ўстановлены жорсткіяшы ліміты.
Для прыкладаў тут быў выкарыстоўваны Express, але тыя ж прынцыпы прыменяюцца і да іншых фрэймворкаў бэкенду, большасць з яых або безпосередна інтэгруюць гэтыя інструменты, або прадаюць ўласныя эквіваленты. Напрыклад, NestJS керуе пераканаленнем запытоў за дапамогою об’ектаў перадачы дадзенняў. У дакументацыі вашаг фрэймворка будзе показана стандартная версія кожнага слоя.
Тыя ж самыя прынцыпы служаю базой для развертванняя ў хмарных сервісах на такіх платформах, як Azure, Google Cloud і AWS, які дадаюць своія власныя шлюзы, службы аутентыкаціі і механізмы карання частоты запытоў. Практыкі, спецыяльныя для хмарных сервісаў, заслуговуюць на аднальныя розгляды, але вышэўзгаданыя шары вже значна спрыяюць таму, каб API прайшла перагляд безпекі.
Ключовыя выводы
- Раздзеляйце перакананне аб наявнасці крэдытных дакументаў і аутентыкацію за кожны запыт: пераканайцеся ў паролі раз, а потым паверніцеся да короткага тэрміnu дзейнасці падписаных токенаў.
- Аутентыкація — гэта не автарызація. Пераканайцеся ў дапуску і ролях, а таксама пераканайцеся ў прыналежнасці кожнага об’екту, які прабывае пад запытом.
- Для даных, якія знаходзяцца у стане спакою, іспользуйце AES-GCM з унікальным IV для кожной операцыі; асиметрычную крэптаграфію застаўляйце для малых значэнняў і абмены кантрамі.
Супаўязаная літэратура
- Калі JWT Auth стае залежным ад стану: праблемы серверных сэсій у Node — Дакладная інфармацыя пра тое, як спискі заблокаваных ключоў і хранальнікі апавежэння робяць метод автанацыі JWT хуткабяжучым, як сэсіі Express на базе Postgres яго спрасцавляюць, і дзе ўсё ж можна выкарыстоўваць JWT.
- Стварэнне аутентыкацыі на аснове JWT, гатовай да выкорыстання ў працоўных сэрвісах, у API на Node.js — як хешаваць паролі, выдаваць тымчасовыя JWT-токены, дадаць токены апавежэння і механізмы анулювання, раздзеляць права доступу, захіствацца ад атак типу brute force і пераканальванне, што аутентыкацыя правільна не вядома.
- Аутентыкацыя з викорыстаннем біаметрычных дадзенняў на iOS і Android з ключамі, прыкреплённымі да апаратуры — дакладнае раз’ясненне, чаму сама тэхнологія Face ID не можа служыць доказам для вашага сэрвера, і як викорыстоўваць ключі Secure Enclave і Android Keystore для падписвання ексклюзыўных запитоў ад сэрвера.
- Адмініструванне файлаў пасля ўзначыць: безпечнае збераганне і выдача файлаў корыстнікаў у Express — Дазнаецеся, дзе аплікацыі Express павінны зберагчы ўзначаныя файлы, як express.static супараднуючы каталогі з URL-адрэсамі, і якія заходы запобегаюць тому, каб узначаныя корыстніками файлы сталі вадой для безпекі.
- Контроль даступу на аднойчыні ў Express за дапамогою JWT і двух мідлвэраў — Дазнаецеся, як застаўіць контроль даступу на аднойчыні ў API Express, спалучаючы мідлвэр аутантыкацыі JWT з функцыяй authorize() для павторнага викорыстоўвання, і калі трэба вярнуць код 401, а калі код 403.