Какова последовательность действий для получения JWT-токена при аутентификации?

«Какова последовательность действий для получения JWT-токена при аутентификации?» — вопрос из категории Безопасность, который задают на 10% собеседований Java Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Процесс получения JWT (JSON Web Token) следует стандартному flow аутентификации:

Основные шаги:

  1. Клиент отправляет учетные данные.

    • Обычно POST-запрос на эндпоинт /login или /oauth/token.
    • Тело запроса содержит username/password, client_id/client_secret (для OAuth2) или refresh token.
      POST /api/auth/login HTTP/1.1
      Content-Type: application/json
      {
      "username": "user@example.com",
      "password": "secure_password"
      }
  2. Сервер аутентификации проверяет данные.

    • Валидирует логин/пароль в БД или через внешний провайдер (LDAP, OIDC).
    • Проверяет активность учетной записи и права доступа.
  3. Сервер генерирует и подписывает JWT.

    • Header: Определяет алгоритм подписи (например, HS256 или RS256).
    • Payload (Claims): Содержит данные (sub, roles, exp — время истечения, iat — время выдачи).
    • Signature: Создается путем подписи header.payload секретным ключом (HMAC) или приватным ключом (RSA).
  4. Сервер возвращает токен клиенту.

    • Чаще всего в теле ответа как JSON-объект.
      {
      "access_token": "eyJhbGciOiJIUzI1NiIs...",
      "token_type": "Bearer",
      "expires_in": 3600
      }

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

// Конфигурация сервера (используя Password Grant)
@Configuration
@EnableAuthorizationServer
public class AuthConfig extends AuthorizationServerConfigurerAdapter {
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
            .withClient("clientapp")
            .secret(passwordEncoder().encode("secret"))
            .authorizedGrantTypes("password", "refresh_token")
            .scopes("read", "write");
    }
}
// Клиентский запрос (с помощью RestTemplate или WebClient)
HttpHeaders headers = new HttpHeaders();
headers.setBasicAuth("clientapp", "secret");
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);

MultiValueMap<String, String> body = new LinkedMultiValueMap<>();
body.add("grant_type", "password");
body.add("username", "user");
body.add("password", "pass");

HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(body, headers);
ResponseEntity<Map> response = restTemplate.postForEntity(
    "http://auth-server/oauth/token",
    request,
    Map.class
);
String jwt = (String) response.getBody().get("access_token");

Best Practices:

  • Всегда используйте HTTPS.
  • Храните секретные ключи в безопасном хранилище (не в коде).
  • Устанавливайте разумное время жизни токена (expires_in).
  • Для хранения на клиенте предпочтительны HttpOnly, Secure куки.