Creato dall'IAMigliorato dalle persone
Riqli · documenti viventi · aggiornati continuamente
Dove la conoscenza dell'IA incontra la pratica umana.
Condividi con amici

Аутентификация, авторизация и работа с сессиями/токенами
Введение в аутентификацию, авторизацию и работу с сессиями/токенами
Аннотация. Курс посвящён фундаментальным и практическим аспектам аутентификации, авторизации и управления сессиями в веб-приложениях. Рассматриваются классические серверные сессии, токенные подходы на основе JWT, делегированная авторизация через OAuth 2.0 и практические аспекты их реализации в Node.js. Особое внимание уделяется различиям между аутентификацией и авторизацией, выбору подходящего механизма под конкретную архитектуру и типичным уязвимостям.
Цель. Сформировать у слушателя целостное понимание механизмов аутентификации и авторизации, дать практические навыки реализации сессионной и токенной аутентификации, а также осознанного выбора подхода в зависимости от требований безопасности и архитектуры приложения.
Результаты обучения. После прохождения курса слушатель сможет: различать аутентификацию и авторизацию; реализовывать сессионную аутентификацию с хранением в Redis; генерировать и проверять JWT access и refresh токены; настраивать OAuth 2.0 flow для сторонних провайдеров; понимать риски безопасности каждого подхода и применять базовые меры защиты (HttpOnly cookies, короткоживущие access токены, ротация refresh токенов).
Целевая аудитория. Backend-разработчики, знакомые с основами Node.js и Express, желающие систематизировать знания о механизмах аутентификации и научиться применять их в production-сценариях.
Раздел 1. Аутентификация и авторизация: базовые понятия
Аутентификация vs авторизация. Аутентификация отвечает на вопрос «кто вы?» — это проверка подлинности предъявленных учётных данных (логин/пароль, сертификат, биометрия). Авторизация отвечает на вопрос «что вам разрешено?» — это проверка прав доступа к ресурсу или операции. Эти этапы различны: пользователь может быть успешно аутентифицирован, но не иметь прав на конкретное действие. Пример: гость на YouTube может смотреть видео без входа в аккаунт, но для загрузки или комментирования требуется авторизация .
Stateless-природа HTTP. Протокол HTTP не сохраняет состояние между запросами. Это означает, что после успешной аутентификации клиент должен при каждом последующем запросе передавать серверу некий идентификатор, подтверждающий сессию. Механизмы такого подтверждения — сессии и токены — и являются предметом дальнейшего изучения .
Жизненный цикл пользователя в системе. Типовой процесс включает четыре этапа: регистрация (создание учётной записи), аутентификация (подтверждение личности), авторизация (проверка прав для конкретных операций), выход из системы (удаление учётных данных на клиенте и/или сервере). Регистрация и аутентификация часто совмещаются: после создания аккаунта пользователь автоматически получает учётные данные и может работать без дополнительного входа .
Какой вопрос решает авторизация?
Что именно разрешено сделать пользователю?
Авторизация проверяет права доступа пользователя к конкретным ресурсам или операциям. Аутентификация же подтверждает подлинность личности, но не определяет объём прав.
Кто именно обращается к системе?
Какой пароль использует пользователь?
Когда пользователь последний раз входил в систему?
Раздел 2. Сессионная аутентификация
Принцип работы сессий. Серверная сессия — классический механизм аутентификации. Пользователь отправляет логин и пароль, сервер проверяет их и создаёт уникальную случайную строку — идентификатор сессии. Этот идентификатор сохраняется на сервере (в базе данных, Redis или памяти) и отправляется клиенту, как правило, через HTTP cookie. При каждом следующем запросе клиент автоматически прикладывает cookie, сервер сверяет идентификатор с хранилищем и, если сессия валидна, выполняет запрос от имени пользователя .
Хранение сессий: выбор хранилища. Для приложений с небольшой или средней нагрузкой (до 50–100 запросов в секунду) подходят реляционные базы, такие как PostgreSQL. Для high-load сценариев и enterprise-приложений оптимален Redis — он хранит данные в оперативной памяти, поддерживает быстрое выставление TTL для ключей и обеспечивает репликацию и бэкапы (RDB, AOF) . В production Redis рассматривается как полноценная часть инфраструктуры, а не как игрушка.
Безопасность сессий. Идентификатор сессии должен передаваться только по HTTPS. Cookie должны иметь атрибуты HttpOnly (недоступность из JavaScript), Secure (только по HTTPS), SameSite (защита от CSRF). В базе данных хранится не сам токен сессии, а его хэш — это защищает от использования украденных данных базы .
Практика: сессии в Express с Redis. Минимальная реализация сессионной аутентификации в Node.js использует связку express-session и connect-redis. Пример конфигурации:
import session from 'express-session';
import RedisStore from 'connect-redis';
import { createClient } from 'redis';
const redisClient = createClient({ url: process.env.REDIS_URL });
await redisClient.connect();
app.use(session({
store: new RedisStore({ client: redisClient }),
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: false,
cookie: {
httpOnly: true,
secure: true,
sameSite: 'lax',
maxAge: 1000 * 60 * 60 * 24
}
}));После успешного входа в сессию сохраняется идентификатор пользователя: req.session.userId = user.id. Мидлвар для защиты маршрутов проверяет наличие req.session.userId .
Плюсы и минусы. Плюсы сессионного подхода: моментальный отзыв доступа (удаление записи из хранилища), состояние всегда актуально, простота ротации. Минусы: требуется общая для всех инстансов сессионная БД, необходима CSRF-защита для небезопасных методов .
Где хранится идентификатор сессии в классической серверной схеме?
На сервере в хранилище (БД, Redis, память), клиент получает только идентификатор
Ключевая особенность сессионного подхода — состояние хранится на сервере. Клиент получает лишь уникальный идентификатор сессии, обычно через cookie, и прикладывает его к каждому запросу.
Полностью на клиенте в localStorage
В JWT-токене, который передаётся в заголовке Authorization
В URL-параметре каждого запроса
Раздел 3. JWT: токены без состояния
Структура JWT. JSON Web Token состоит из трёх частей, разделённых точкой: header.payload.signature. Header описывает алгоритм подписи (HS256, RS256). Payload содержит claims — стандартные (sub, iat, exp) и кастомные поля (userId, role). Signature — криптографическая подпись, гарантирующая целостность токена. Важно: payload не шифруется, а только кодируется в base64url. Нельзя помещать в payload пароли, секретные ключи или персональные данные .
Access и refresh токены. Классическая схема использует два типа токенов. Access-токен короткоживущий (типично 15 минут) и используется для доступа к защищённым ресурсам. Refresh-токен долгоживущий (до 30 дней) и служит для получения нового access-токена без повторного ввода пароля. Разные секреты для access и refresh — обязательное условие: компрометация refresh не должна давать немедленный доступ к ресурсам .
Проверка токена. На защищённых маршрутах токен извлекается из заголовка Authorization: Bearer <token>, затем проверяется подпись и срок действия через jwt.verify. Если проверка успешна, данные из payload становятся доступны в req.user .
Практика: генерация токенов в Node.js. Пример сервиса для выдачи пары токенов:
import jwt from 'jsonwebtoken';
const ACCESS_TTL = '15m';
const REFRESH_TTL = '30d';
function issueTokens(user) {
const access = jwt.sign(
{ sub: user.id, role: user.role },
process.env.JWT_ACCESS_SECRET,
{ expiresIn: ACCESS_TTL }
);
const refresh = jwt.sign(
{ sub: user.id, jti: crypto.randomUUID() },
process.env.JWT_REFRESH_SECRET,
{ expiresIn: REFRESH_TTL }
);
return { access, refresh };
}Refresh-токен рекомендуется сохранять в HttpOnly cookie, access-токен возвращать в теле ответа. При логине проверять пароль через bcrypt .
Ротация refresh-токенов. При каждом использовании refresh-токена следует выдавать новый refresh и инвалидировать старый (rotate on use). Это снижает окно атаки при утечке. Дополнительно можно включить reuse detection — если старый refresh-токен предъявлен повторно, все сессии пользователя аннулируются .
Какие данные хранятся в payload JWT?
Claims — стандартные (sub, iat, exp) и кастомные поля (userId, role)
Payload содержит утверждения (claims) о пользователе и токене. Стандартные claims включают subject, issued at, expiration. Кастомные поля добавляются для передачи идентификатора пользователя, роли и другой необходимой информации.
Пароль пользователя в зашифрованном виде
Секретный ключ сервера для подписи
Полная история действий пользователя
Раздел 4. OAuth 2.0: делегированная авторизация
Суть OAuth 2.0. OAuth 2.0 — протокол делегированной авторизации. Он позволяет владельцу ресурса предоставить стороннему приложению ограниченный доступ к своим данным, не передавая пароль. Аналогия: гость отеля заказывает доставку еды в номер. Курьер получает специальный пропуск, который открывает ровно одну дверь в течение ограниченного времени, но не даёт доступа к другим номерам и не раскрывает личность гостя .
Четыре роли OAuth. В протоколе выделяют: владельца ресурса (пользователь, который авторизует приложение), клиента (приложение, запрашивающее доступ), сервер авторизации (проверяет учётные данные и выдаёт токены), ресурсный сервер (хранит защищённые данные и принимает access-токены) .
Основные flow. Authorization Code Flow — для веб-приложений с бэкендом, самый безопасный: токены не попадают в браузер. Client Credentials Flow — для межсервисного взаимодействия без участия пользователя. Implicit Flow — устаревший, использовался в SPA, признан небезопасным и исключён из OAuth 2.1, поскольку access-токен напрямую попадает в браузер и уязвим к XSS .
Практика: OAuth с Passport.js. Для интеграции OAuth 2.0 в Node.js часто используется Passport.js. Пример настройки Google Strategy:
passport.use(
new GoogleStrategy({
clientID: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
callbackURL: '/api/auth/google/callback',
},
async (accessToken, refreshToken, profile, done) => {
let user = await db.users.findOne({ googleId: profile.id });
if (!user) {
user = await db.users.create({
googleId: profile.id,
email: profile.emails[0].value,
name: profile.displayName,
});
}
return done(null, user);
})
);Маршрут /auth/google инициирует редирект на страницу согласия Google, /auth/google/callback обрабатывает возврат с кодом авторизации и обменивает его на access-токен .
Типичные уязвимости OAuth. Недостаточный контроль redirect_uri позволяет злоумышленнику перенаправить код авторизации на свой сервер. Повторное использование кода авторизации (code injection) — если код не инвалидируется после обмена на токен. Использование кода авторизации другим клиентом — если сервер не проверяет соответствие client_id и кода. Проверка времени жизни токена — access-токен должен истекать через указанный expires_in .
Какой OAuth flow рекомендуется для веб-приложений с бэкендом?
Authorization Code Flow
Authorization Code Flow — самый безопасный вариант для серверных веб-приложений: access-токен не попадает в браузер, обмен кода на токен происходит на бэкенде. Implicit Flow признан небезопасным и исключён из OAuth 2.1.
Implicit Flow
Client Credentials Flow
Password Grant Flow
Раздел 5. JWKS: управление публичными ключами
Проблема распределённых систем. Вспомним пример с отелем: консьерж (AuthService) выдаёт постояльцам браслеты — подписанные JWT. Первоначально консьерж вручную раздаёт публичный ключ для проверки подписи каждому сотруднику отеля. Но когда отель растёт, появляются новые сервисы (спортзал, спа, рестораны), и сотрудников становится слишком много, чтобы каждому выдать ключ лично. Кроме того, ключи периодически нужно менять — например, при компрометации или плановой ротации. Возникает вопрос: как быстро и безопасно распространять актуальные публичные ключи среди всех сервисов?
Решение: JWKS endpoint. JWKS (JSON Web Key Set) — это специальный эндпоинт, который хранит набор актуальных публичных ключей и позволяет любому сервису самостоятельно их загрузить. Это электронная витрина с ключами, которой доверяют все участники системы. Когда сервис получает JWT, он извлекает идентификатор ключа (kid) из заголовка токена, находит соответствующий публичный ключ в JWKS и проверяет подпись. Если AuthService меняет ключ, JWKS обновляется автоматически, и все сервисы получают новый ключ без ручной настройки.
Структура JWK и JWKS. JWK (JSON Web Key) — это представление одного криптографического ключа в формате JSON. Основные поля JWK: kid (идентификатор ключа), use (назначение: sig для подписи, enc для шифрования), kty (тип ключа, например RSA или EC), alg (алгоритм, например RS256). JWKS — это массив, содержащий один или несколько JWK. Такой массив позволяет поддерживать ротацию ключей и использовать разные алгоритмы одновременно.
Раздел 7. Практические аспекты ротации refresh-токенов
Практика: загрузка и кэширование JWKS. В распределённой системе микросервис не должен запрашивать JWKS при каждом входящем запросе — это создаст лишнюю нагрузку и задержки. Правильный подход: загрузить JWKS один раз при старте и периодически обновлять его в фоне. Пример реализации на Node.js:
import fetch from 'node-fetch';
const JWKS_URL = 'https://auth.example.com/.well-known/jwks.json';
let jwksCache = null;
let lastFetch = 0;
const CACHE_TTL = 3600_000; // 1 час
async function getPublicKey(kid) {
const now = Date.now();
if (!jwksCache || now - lastFetch > CACHE_TTL) {
const res = await fetch(JWKS_URL);
jwksCache = await res.json();
lastFetch = now;
}
const jwk = jwksCache.keys.find(k => k.kid === kid);
if (!jwk) throw new Error(`Unknown key id: ${kid}`);
return crypto.createPublicKey({ key: jwk, format: 'jwk' });
}При проверке токена сначала декодируйте заголовок, извлеките kid, затем получите соответствующий публичный ключ из кэша. Если ключ не найден — принудительно обновите JWKS (возможно, ключ был только что добавлен). Не пытайтесь «угадать» ключ или использовать первый попавшийся — это нарушит безопасность.
Ротация ключей. JWKS должен содержать как текущий активный ключ, так и предыдущий — чтобы токены, выпущенные до ротации, всё ещё могли быть проверены. Когда все старые токены истекут, предыдущий ключ можно удалить из JWKS. Такой подход обеспечивает бесшовную ротацию без сбоев в работе сервисов.
Зачем нужен параметр kid в заголовке JWT?
Проблема сетевых сбоев при ротации. Механизм ротации refresh-токенов создаёт специфическую проблему, которой не было при статичных токенах. Представим: клиент отправляет refresh-токен на сервер, но ответ теряется из-за обрыва сети. Клиент не получил новую пару токенов, но сервер уже инвалидировал старый refresh-токен и выдал новый. При повторной попытке клиент отправит тот же самый refresh-токен, который сервер теперь считает недействительным. С точки зрения сервера это выглядит как повторное использование украденного токена — срабатывает защита, и вся сессия (весь token family) аннулируется. Пользователь внезапно оказывается разлогинен без видимой причины.
Стратегия атомарного хранения токенов. Чтобы избежать ложных срабатываний защиты, клиент должен реализовать атомарное обновление токенов. Перед отправкой refresh-запроса клиент сохраняет состояние «обновление в процессе». Только после успешного получения ответа с новой парой токенов старое значение заменяется новым. Если сеть оборвалась, клиент может выполнить одну повторную попытку с тем же refresh-токеном — сервер, реализующий корректную логику ротации, должен распознать это как легитимный retry и не аннулировать сессию. Если и повторная попытка не удалась, клиент обязан принудительно инициировать повторную аутентификацию.
Обработка ошибок 401 при повторном использовании. Если сервер всё же вернул ошибку 401 на повторно использованный refresh-токен, это означает, что защита сработала и весь grant (все токены, выданные в рамках этой сессии) аннулирован. В этом случае клиент не должен пытаться «исправить» ситуацию повторными запросами — это только усугубит положение. Единственное правильное действие — перенаправить пользователя на страницу входа. Это не ошибка клиента, а защитный механизм, который сработал корректно.
Установка разумного TTL. Ротация не означает, что refresh-токены живут вечно. Для enterprise-приложений типичным значением является 30 дней. Важно также установить абсолютный срок жизни сессии — даже если refresh-токен постоянно ротируется, через определённое время (например, 90 дней) пользователь должен пройти повторную аутентификацию. Это ограничивает потенциальный ущерб от длительной компрометации.
Чтобы указать, какой именно публичный ключ из JWKS использовать для проверки подписи
kid (key ID) позволяет сервису однозначно идентифицировать ключ, которым подписан токен. Без него при наличии нескольких ключей в JWKS было бы невозможно определить, какой именно ключ применить для проверки.
Для шифрования payload токена
Почему при ротации refresh-токенов клиент должен реализовать атомарное хранение состояния обновления?
Для указания срока действия токена
Для передачи роли пользователя
Чтобы избежать ложного срабатывания защиты от кражи токена при сетевом сбое