Как происходит процесс аутентификации и выдачи токена (OAuth 2.0 / JWT)?

Ответ

Процесс аутентификации по схеме OAuth 2.0 с выдачей JWT-токена (поток Resource Owner Password Credentials или Authorization Code):

1. Запрос на получение токена: Клиентское приложение отправляет HTTP POST-запрос на эндпоинт аутентификации сервера авторизации (Authorization Server).

Пример тела запроса для потока Password Credentials:

POST /oauth/token HTTP/1.1
Host: auth-server.com
Content-Type: application/x-www-form-urlencoded
Authorization: Basic base64(client_id:client_secret)

grant_type=password&username=user&password=pass&scope=read write

2. Валидация учетных данных: Сервер авторизации:

  • Проверяет client_id и client_secret (для confidential clients).
  • Аутентифицирует пользователя по username и password (сверяя с БД, LDAP, etc.).
  • Проверяет запрошенные scope на соответствие правам клиента.

3. Генерация JWT-токена: Если валидация успешна, сервер создаёт JWT (JSON Web Token), состоящий из трёх частей:

  • Header: Алгоритм подписи (например, {"alg": "HS256", "typ": "JWT"}).
  • Payload (Claims): Данные (например, {"sub": "123", "username": "john", "roles": ["USER"], "exp": 1678886400}).
  • Signature: Цифровая подпись, созданная с использованием секретного ключа (HMAC) или приватного ключа (RSA).

4. Отправка ответа клиенту: Сервер возвращает JSON-ответ с токеном и метаданными.

{
  "access_token": "eyJhbGciOiJIUzI1NiIs...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "def50200e2b4c...",
  "scope": "read write"
}

5. Использование токена клиентом: Клиент сохраняет токен (в памяти, localStorage или безопасных куках) и использует его для доступа к защищённым ресурсам (Resource Server), добавляя в заголовок:

GET /api/protected-resource HTTP/1.1
Host: api-server.com
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...

Пример кода на Spring Security (упрощённо):

@RestController
public class AuthController {
    @PostMapping("/login")
    public ResponseEntity<AuthResponse> login(@RequestBody LoginRequest request) {
        // 1. Аутентификация
        Authentication authentication = authenticationManager.authenticate(
            new UsernamePasswordAuthenticationToken(request.getUsername(), request.getPassword())
        );
        // 2. Генерация JWT
        String jwt = jwtTokenUtil.generateToken(authentication);
        // 3. Ответ
        return ResponseEntity.ok(new AuthResponse(jwt));
    }
}

Важно: Поток password рекомендуется только для доверенных клиентов (например, собственные мобильные приложения). Для веб-приложений предпочтительнее поток Authorization Code с PKCE.

Ответ 18+ 🔞

Ну вот, опять эти ваши OAuth и JWT, блядь. Сидишь такой, пьешь кофе, думаешь — ну щас всё просто, логин-пароль, и вперёд. А нихуя! Тут целый цирк с конями, ебать мои старые костыли.

Смотри, как эта шарманка крутится, если по-простому.

1. Начинается цирк — запрос на токен. Твоё приложение, как обоссанный щенок, бежит к серверу авторизации и стучится: «Пусти, я свой!». И шлёт он не абы что, а вот такую хуйню:

POST /oauth/token HTTP/1.1
Host: auth-server.com
Content-Type: application/x-www-form-urlencoded
Authorization: Basic base64(client_id:client_secret)

grant_type=password&username=user&password=pass&scope=read write

Видишь этот grant_type=password? Это как сказать: «Эй, мужик, я доверенный, вот мой паспорт и пароль от жены, дай пропуск». Но так можно только с очень доверенными клиентами, а то сейчас налетят пидары и всё просрут.

2. Сервер начинает воротить нос. Он получает эту простыню и думает: «А не пиздят ли мне?». И начинает проверять:

  • client_id и client_secret — эти ксивы левые или нет?
  • username и password — а этот чел в базе есть или это опять Вася из соседнего подъезда пытается зайти?
  • scope=read write — а этому клиенту вообще можно столько прав? Может, он только на read потянет?

3. Если всё чики-пуки, начинается магия — генерация JWT. Это не просто строка, блядь, это трёхслойный бутерброд, подписанный по всем правилам:

  • Header (Шапка): Тут написано, какой алгоритм подписи. Типа {"alg": "HS256", "typ": "JWT"}. Читай как «рубить буду так-то».
  • Payload (Начинка): А вот тут уже сочное. Кто юзер (sub), как зовут (username), какие у него роли (roles), и главное — когда сдохнет этот токен (exp). {"sub": "123", "username": "john", "roles": ["USER"], "exp": 1678886400}.
  • Signature (Подпись): А это, сука, чтобы умники не подделали. Берётся шапка и начинка, склеиваются, и нахуй шифруются секретным ключом. Получается такая хитрая жопа, которую не взломать.

4. Выдача пропуска. Сервер, довольный собой, плюёт тебе в ответ JSON'ом:

{
  "access_token": "eyJhbGciOiJIUzI1NiIs...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "def50200e2b4c...",
  "scope": "read write"
}

Вот он, святой грааль! access_token — это твой пропуск на час (expires_in: 3600). refresh_token — это как запасная почка, если основной токен откинулся, можно им новый выбить.

5. И пошло-поехало. Клиент хватает этот токен, суёт его в хранилище (только, бля, не в localStorage на виду, дурачок!), и теперь с ним можно ходить по всем защищённым API, как царь. Просто добавляешь в заголовок:

GET /api/protected-resource HTTP/1.1
Host: api-server.com
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...

И ресурсный сервер, увидев Bearer, говорит: «А, ну ладно, проходи».

А вот, блядь, как это выглядит внутри на Spring Security, если очень упрощённо:

@RestController
public class AuthController {
    @PostMapping("/login")
    public ResponseEntity<AuthResponse> login(@RequestBody LoginRequest request) {
        // 1. Аутентификация (проверяем, не мудак ли)
        Authentication authentication = authenticationManager.authenticate(
            new UsernamePasswordAuthenticationToken(request.getUsername(), request.getPassword())
        );
        // 2. Генерация JWT (если не мудак — печатаем пропуск)
        String jwt = jwtTokenUtil.generateToken(authentication);
        // 3. Ответ (получай и кайфуй)
        return ResponseEntity.ok(new AuthResponse(jwt));
    }
}

И главное, нахуй, запомни: этот поток password — он только для своих, для доверенных клиентов! Типа твоего родного мобильного приложения. Для всяких левых веб-приложений надо использовать Authorization Code с PKCE, это чтобы тебя не наебали по-крупному. А то придётся потом кричать «Муму!», как тот глухонемой Герасим, но уже ни хуя не поможет.