Сэшны протыкса JWT-аўтантыфікацыя: выбор правильнага модэлю аутантыфікацыі для Node.js
Дазвольце даклэ расказаць, у чым насправдзе разлік межа сэсіямі і JWT-файламі пад аутантыкацыяй у Node.js, як функцыонуюць кожны з іх, і як выбраць аднойчы тое, што не будзе прыкро пазніяй.
Проблема, яка існуе ў обох падходах
У HTTP няма вбудованай памяці пра ўсё, што было у пытанні ранейшага запиту. Кожны новы запит выглядае так, нібы ён прыходзіць ад абсалютна незнаёмага, якщо толькі ён не несе з сабой доказа ідэнтыфікаціі. Сесіі та JWT-клейтвы рашаюць гэту проблему адносова тым самым фундаментальным спосабом: яны даюць кліенту фрагмент дадзейнаў, які ён можа вернуць у кожны наступны запит. Тое, што насправды ўскладнюе ўжыццё іх, — гэта характер таго фрагмента дадзейнаў, а ўсё важлівейшае — тое, дзе фактычна знаходзіцца автаномныя даны пра тое, „хто ўвійшоў у систему“.
Варыянт першы: Сесіі
У схеме, якая базуецца на сесіях, автаномнае права мае сервер. Калі корыстнік увайшоў у систему, сервер стварае вялікі адбыточны ідэнтыфікатор сесіі, зберагае рэальную інформацыю пра корыстніка пад гэтым ідэнтыфікаторам у такіх средствах, як Redis або база дадзейнаў, і дае кліенту толькі сам ідэнтыфікатор, які зазвычай зберагаецца ў куку.
app.post("/login", async (req, res) => {
const user = await authenticate(req.body.email, req.body.password);
const sessionId = generateSecureId();
await redis.set(`session:${sessionId}`, JSON.stringify({ userId: user.id }), "EX", 86400);
res.cookie("sessionId", sessionId, { httpOnly: true, secure: true });
res.json({ success: true });
});
app.use(async (req, res, next) => {
const sessionId = req.cookies.sessionId;
const session = await redis.get(`session:${sessionId}`);
req.user = session ? JSON.parse(session).userId : null;
next();
});
Пачаткуючы з гэтага моманту, кожны прыходзячы выкалека запускае пошук у там, дзе зберагаюцься даныя сесіі. Гэты адзін механізм поясняе як тое, чаму сесіі ўжытковыя, так і тое, чаму яны ствараюць додатковую навантажэння.
Плюсы: вы маглівы абсолютна і негайна контроль над тым, хто залишаецца запісаным. Заканчэнне сесіі, незалежна ад таго, чыя прычына — выход корыстніка, перазначэнне пароля чы адміністратар, які закрыў скомпрометаваны акаунт, — гэта проста выдаленне дакумента з базы сесій. Не трэба чакаць, пакуль ўсё сама заканчыцца за час абліквідацыі.
Мінусы: тепер кожны выкалека вымагае двухкроковага пераводу да базы сесій, што дадае невялікую, але рэальную затрэмтанасць і навантажэння на інфраструктуру. Це таксама ператварае вашу базу сесій у спяльны стан, да якога павінны дасягнуць усе вашы серверы, што стае сэрйозной архітектурнай проблемай як толькі вы расширюецеся за межы адзінаго сервера.
Варыянт два: JWT
JWT (JSON Web Token) працюе інакш: сервер падписвае токен, які вже мае даныя пользователя, а кліент перасылае гэты самы токен з кожным запитам. Сервер пераканальвае падпис і давае значэнне там, што ў токене, без патрэбы шукаць інфармацыю ў іншых месцах.
app.post("/login", async (req, res) => {
const user = await authenticate(req.body.email, req.body.password);
const token = jwt.sign({ userId: user.id }, process.env.JWT_SECRET, { expiresIn: "1h" });
res.cookie("token", token, { httpOnly: true, secure: true });
res.json({ success: true });
});
app.use((req, res, next) => {
try {
const decoded = jwt.verify(req.cookies.token, process.env.JWT_SECRET);
req.user = decoded.userId;
} catch {
req.user = null;
}
next();
});
Не выконваецца жадных запытоў да базы дадзеных і не выконваецца жадных запытоў да Redis. Сам па сабе крыптаграфічны падпис дастатковы, ўбеджваючы, што токен не быў пахістаны з моменту його выдачы.
Плюсы: няма неабходнасці шукаць інфармацыю ў базе дадзеных чы кэшы з кожным запытам, што значна прыбліжае працэсы, а таксама няма спакульнага хранення сэсій, да якога павінны дасягнуць усе серверы, што спрощае горизонтальнае масштабаванне і робіць архітектуры безстацыйных мікросэрвісаў значна простымі для разумення.
Компроміс: калі чак JWT быў выдан, сервер не мае можлівасці анулювацыя яго прыбліжна да часу выканання. Якщо токен будзе вкрадзены, або якшчы доступ пользователя трэба негайна паставіць пад контроль, сервер не можа нічога адмахнуць, таму што спачатку не было схована нічога центральнага. Гэты самэўплата часта застае людзей без адпаведных заходоў, зазвычай пасля таго, як яны вже спроекавалі свой система на адной падставе, што JWT — гэта проста быстрейшая, лепшая версія сесіі.
Хыбныя уявленні, якія ствараюць рэальныя проблемы
Фраза „JWT-ы ў стане без стану“ часта выкорыстоўваецца як безумовная перавага, таму люди не разумеюць яе справжньяго значэння: наявнасць у стане без стану таксама означае, што па зялёнай лініі яны ўсталяваны без можлівасці анулювання. Якщо кукі сесіі будзе скомпрометавана, яе проста трэба адмахнуць. У той час як вкрадзены JWT застаецца абсолютна дыяўальным і цэлкам падтрымваным вашым серверам працэс часу, пакуль трывае тымчасавы період його дзейнасці, як толькі вы самі не створылі дадатковых мераў, каб гэта запобiec.
Тыповы спосаб рашэння, які выбіраюць люди, — це падтрымка спіса заблокаваных токенам:
app.use(async (req, res, next) => {
try {
const decoded = jwt.verify(req.cookies.token, process.env.JWT_SECRET);
const isRevoked = await redis.get(`revoked:${decoded.jti}`);
if (isRevoked) throw new Error("Token revoked");
req.user = decoded.userId;
} catch {
req.user = null;
}
next();
});
Гэта дзеяўчы, але прыглядзіцеся цікава да таго, што вы ўжо зрабілі: вы вернуліся да перагляду кожнай запыткі ў спяльным хранілішчы дадзэнняў, што якраз і є тым надтрымлівам, ад каго якраз і павінны былі пазбавіцца JWT. У гэты момент у вас больш не існуе системы без стану. У вас ёсць система, базаваная на сесіях, якая маскуюцца пад іншае, з болей заплутаным спосабам рашэння проблем.
Тады каторую ж насправе трэба вжываць?
Выбірайте сесіі, калі: вам патрэбна негайная і надзеяна ануляцыя (думаючы пра дапрыемкі для банкавання, панелі адарбніка чы ўсё, дзе важліва безпека), вы запускаеце адну аплікацыю за дапамою балансэра навантажэння, які можа без вялікіх проблем выкарыстоўваць спяльнае хранілішча сесій, або ж вам проста лепей падтрымваць едынага джерела правды, чым кераваць трымкамі токенаў і логікаю блак-ліста.
Іспользуйце JWT-ы калі: вы керуеце аутэнтыкацыяй у сапраўдна незалежных службах, якія не патрабуюць прымусовага прыступу да аднаго спільнага хранальніка сесый, вы ствараеце систему, у якой корыстэнне короткавечных токенаў (хвіліны замест дзён) дазволяе прыйняць можлівасць перыяду паўтарнага заборання, або фактычная прычына, чаму вы хочаце безстановую модель, — гэта тое, што вы обслужваеце многа незалежных корыстувачаў API, а не толькі таму, што гэта звучыць прыемнаясцю.
Адзін нюанс, якій варта ўвага: на практыцы большасць систем для вырабоцтва не выбірае адну з сторон без альтэрнатыў. Формата, да якога воні прыходзяць, — це короткагалоўкіе JWT-токены, якія союзуюцца з токенам апавежэння, які зберагаецца на стороне сервера. Цэлая схема ўскладнена, а не двухкомпонентная: сесіі керуюць довгастралявым довяроўчам стацусам і процэсам анулювання, тады як JWT-токены забезпечваюць короткія, безстацусныя періяды верыфікацыі межу ўсім гэтым. Якщо вы спрыяжаеце «сесія проты JWT» як выбор между двума альтэрнатывамі, гэта зазвычай ўскладненне, якое свідчыць пра тое, што вы ўсё яшчэ не досяглі такога масштабу, калі такая гібрыдная схема становіцься доцэльной, тое значыць простая сесія, верагатна, будзе чыстасцю і простасцю кращай пачатковай точкай.
Фактычны выбор
Пад спуском, пытанне ніколі не было чыста тэхнічная, на самай працо ёна стосуецца выбору таго, дзе будзе практыкувана вартасць. Сесіі налагоджуюць гэтую вартасць за кожны адзін запит, але ў замяну вы отрымваете кантроль, які завжды є актуальным, ніколи не застарэлым. JWT-ы усунуць гэтую плату за кожны запит, але цінаю для гэтага є перыяд часу, калі тое, што ваш сервер сачытае, і тое, што ў рэальнасці правда, можа тыхо адступіць адзін ад другога. Жадны з гэтых падходоў не заслуговае на атрыбут «савэцкі» чы «застарэлы», незалежна ад таго, як часта ў дыялогах па ўсё гэтае ставятся акценты. Це проста два разныя спосабы рашэння той самай дилемы, якая ніколі насправдзе не зникае.
Спадневана літэратура
- Стратэгія апдзеючых токенаў для систем атрыбутацыі Node.js — Дазвольце вам дазнацца, як проектаваць, зміняць, анулюваць і безпечна хаваць апдзеючыя токены ў Node.js, каб кража токенаў і выйшчы з системы працавалі так, як і планавалася.
- Паспяшнае раз'ясненне стрымоў у Node.js: як выправіць збой сервера чераз нехватку памяці — Дазвольце вам дазнацца, чым вызваны збой сервера Node.js пад час завантажэння цэлых файлоў у памяць, і як чытальныя, запісныя, двухнаправленыя і трансфарматывальныя стрымы выправляюць гэты проблема за дапамогою механізма backpressure.