Как работает технология аутентификации на базе JWT (JSON Web Token)?

Ответ

JWT — это открытый стандарт (RFC 7519) для создания токенов доступа, которые позволяют безопасно передавать информацию между сторонами в виде компактного JSON-объекта. Токен подписан цифровой подписью, что позволяет проверять его целостность и подлинность.

Структура JWT: Токен состоит из трех частей, разделенных точками: header.payload.signature

  1. Header (Заголовок): Содержит тип токена (JWT) и алгоритм подписи (например, HS256 или RS256).
    {
      "alg": "HS256",
      "typ": "JWT"
    }
  2. Payload (Полезная нагрузка): Содержит утверждения (claims) — утверждения о пользователе и дополнительных данных. Стандартные claims: sub (subject — идентификатор пользователя), exp (expiration time — срок действия), iat (issued at — время выдачи).
    {
      "sub": "1234567890",
      "name": "John Doe",
      "role": "admin",
      "exp": 1516239022
    }
  3. Signature (Подпись): Создается путем кодирования header и payload в Base64Url, их объединения с точкой и последующей подписи с использованием секретного ключа (для HMAC) или приватного ключа (для RSA) по алгоритму, указанному в header. HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)

Типичный поток аутентификации с JWT:

  1. Логин: Пользователь отправляет учетные данные (логин/пароль) на сервер аутентификации.
  2. Верификация и генерация токена: Сервер проверяет учетные данные и, если они верны, генерирует JWT, подписывает его секретным ключом и отправляет клиенту (обычно в теле ответа или в cookie).
  3. Хранение и отправка токена: Клиент (браузер, мобильное приложение) сохраняет токен (часто в localStorage или HttpOnly cookie) и включает его в заголовок Authorization последующих запросов к защищенным API.
    Authorization: Bearer <your-jwt-token>
  4. Верификация токена на защищенных ресурсах: Сервер (или API-шлюз) получает токен, проверяет его подпись на валидность с помощью секретного/публичного ключа, убеждается, что срок действия (exp) не истек, и извлекает claims (например, role) для авторизации доступа к ресурсу.

Ключевые преимущества:

  • Бессостояние (Stateless): Серверу не нужно хранить сессию пользователя. Вся необходимая информация содержится в самом токене.
  • Масштабируемость: Легко масштабировать, так как любой сервер с секретным/публичным ключом может проверить токен.
  • Гибкость: В payload можно включать любые пользовательские данные (claims).

Критические моменты безопасности:

  • Секретный ключ: Должен храниться в безопасности на сервере. Компрометация ключа позволяет злоумышленнику генерировать любые токены.
  • Небезопасное хранение на клиенте: Хранение в localStorage уязвимо к XSS-атакам. Более безопасный вариант — HttpOnly, Secure, SameSite cookies.
  • Невозможность отзыва: JWT действует до истечения срока (exp). Для реализации мгновенного выхода (logout) требуется дополнительная архитектура (черные списки токенов, короткий срок жизни + refresh tokens).

Пример генерации и проверки токена на C# (библиотека System.IdentityModel.Tokens.Jwt):

// Генерация токена
var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("your_super_secret_key"));
var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);

var claims = new[]
{
    new Claim(JwtRegisteredClaimNames.Sub, "user123"),
    new Claim("role", "admin")
};

var token = new JwtSecurityToken(
    issuer: "your_issuer",
    audience: "your_audience",
    claims: claims,
    expires: DateTime.Now.AddMinutes(30),
    signingCredentials: credentials
);

var tokenString = new JwtSecurityTokenHandler().WriteToken(token);

// Проверка токена
var tokenHandler = new JwtSecurityTokenHandler();
tokenHandler.ValidateToken(tokenString, new TokenValidationParameters
{
    ValidateIssuerSigningKey = true,
    IssuerSigningKey = securityKey,
    ValidateIssuer = true,
    ValidIssuer = "your_issuer",
    ValidateAudience = true,
    ValidAudience = "your_audience",
    ValidateLifetime = true,
    ClockSkew = TimeSpan.Zero // Точная проверка времени
}, out SecurityToken validatedToken);

Ответ 18+ 🔞

А, JWT, ёпта! Ну это ж классика, как бутерброд с колбасой в мире авторизации. Смотри, сейчас разжую, как для дебила, но ты не обижайся — просто чтоб на пальцах.

Представь, что ты приходишь в клуб, а тебе на руку штамп ставят. Ты потом с этим штампом ходишь, тебе и в бар наливают, и в туалет пускают. JWT — это и есть такой цифровой штамп, только вместо чернил — криптография, блядь.

Из чего эта хрень состоит? Токен — это три куска, склеенные точками: голова.тело.подпись. Всё в кодировке Base64Url, но это так, техническая хуйня.

  1. Голова (Header) — там написано, что это за тип и чем подписано. Типа инструкция для проверяющего.
    {
      "alg": "HS256", // Алгоритм подписи, например, HMAC-SHA256
      "typ": "JWT"    // Тип — JWT, чё уж там
    }
  2. Тело (Payload) — а вот тут самое интересное лежит. Всякие утверждения (claims) про пользователя: кто он, какие права имеет, когда токен сдохнет. Можно свои поля пихать.
    {
      "sub": "1234567890", // Кто (subject)
      "name": "Вася Пупкин",
      "role": "admin",      // Роль — админ, значит, можно всё
      "exp": 1516239022    // Время смерти токена (timestamp)
    }
  3. Подпись (Signature) — вот тут магия. Берут закодированные голову и тело, склеивают точкой и ебут алгоритмом с секретным ключом. Получается этакая печать, которую не подделать, если ключ не утек. HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)

Как этим пользуются? Очень просто:

  1. Логин. Ты шлёшь логин-пароль на сервер. Сервер проверяет — не мудак ли ты. Если не мудак, он генерит этот JWT, подписывает своим секретным ключом и шлёт тебе обратно.
  2. Хранение. Ты этот токен куда-то суёшь. В браузере — либо в localStorage (но это рискованно, могут через XSS украсть), либо в HttpOnly куки (безопаснее). В мобилке — в защищённое хранилище.
  3. Использование. Когда тебе нужно что-то от защищённого API, ты просто прикрепляешь этот токен в заголовок запроса:
    Authorization: Bearer <твой_длинный_токен_тут>
  4. Проверка. Сервер или API-шлюз видят этот заголовок, вытаскивают токен, проверяют подпись своим секретным (или публичным) ключом. Если подпись сошлась — значит, токен настоящий, не поддельный. Потом смотрят на срок (exp) — не просрочен ли. Если всё ок, вытаскивают из тела (payload) данные, например, роль "admin", и решают — пускать тебя дальше или послать на хуй.

Чем хорош?

  • Stateless (без состояния). Серверу не нужно помнить сессии в памяти или базе. Всё нужное прямо в токене. Масштабируется легко — поставил десять серверов, и все могут проверять токен, зная один секретный ключ.
  • Универсальный. В payload можно запихнуть что угодно — имя, почту, права, цвет любимых трусов.

Где подвох? Безопасность, сука!

  • Ключ. Секретный ключ на сервере — это святое. Утечёт ключ — злоумышленник сможет лепить любые токены, будто он Бог. Хранить надо как зеницу ока.
  • Хранение на клиенте. localStorage — лакомый кусок для XSS. Куки с флагами HttpOnly, Secure, SameSite — надёжнее.
  • Не отозвать. Пока токен не просрочился (поле exp), он жив. Нельзя просто так взять и "разлогинить" пользователя. Для этого нужны дополнительные костыли: либо чёрные списки токенов (что убивает stateless), либо короткая жизнь access-токена + refresh-токены.

Пример на C# (чтоб было понятно, как код выглядит):

// Создание токена
var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("твой_супер_секретный_ключ_сюда"));
var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);

var claims = new[]
{
    new Claim(JwtRegisteredClaimNames.Sub, "user123"),
    new Claim("role", "admin")
};

var token = new JwtSecurityToken(
    issuer: "твой_сервис",
    audience: "твоё_приложение",
    claims: claims,
    expires: DateTime.Now.AddMinutes(30), // Живёт 30 минут
    signingCredentials: credentials
);

var tokenString = new JwtSecurityTokenHandler().WriteToken(token); // Вот он, строка токена

// Проверка токена
var tokenHandler = new JwtSecurityTokenHandler();
tokenHandler.ValidateToken(tokenString, new TokenValidationParameters
{
    ValidateIssuerSigningKey = true,
    IssuerSigningKey = securityKey,        // Проверяем подпись тем же ключом
    ValidateIssuer = true,
    ValidIssuer = "твой_сервис",           // Должен совпасть issuer
    ValidateAudience = true,
    ValidAudience = "твоё_приложение",     // Должен совпасть audience
    ValidateLifetime = true,               // Проверяем, не протух ли
    ClockSkew = TimeSpan.Zero              // Без допуска по времени, строго
}, out SecurityToken validatedToken);      // Если всё ок — validatedToken будет не null

Короче, JWT — инструмент мощный, но как топор: если в руках идиота, можно себе же ноги отрубить. Главное — ключи хранить в секрете, сроки жизни делать адекватные и не пихать в payload чувствительные данные (тип паролей), потому что payload легко раскодировать и прочитать, блядь. Подпись защищает от подделки, но не от чтения.