Как Spring Security связывает HTTP-запрос с конкретным пользователем (клиентом)?

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

Ответ

Spring Security связывает запрос с пользователем через объект Authentication, который хранится в SecurityContext, привязанном к текущему потоку выполнения (ThreadLocal).

Последовательность при обработке запроса:

  1. SecurityContextPersistenceFilter восстанавливает SecurityContext для текущего запроса (например, из HTTP-сессии) или создает новый.
  2. Фильтр аутентификации (например, UsernamePasswordAuthenticationFilter для формы логина или BearerTokenAuthenticationFilter для JWT) проверяет запрос.
  3. При успешной проверке создается заполненный объект Authentication (с principal, authorities) и устанавливается в SecurityContext.
  4. Контекст сохраняется в SecurityContextHolder, делая пользователя доступным на протяжении всего запроса.

Пример получения данных текущего пользователя в коде:

// Стандартный способ получения аутентификации
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

if (authentication != null && authentication.isAuthenticated()) {
    String username = authentication.getName();
    Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
    // ... работа с данными пользователя
}

Ключевые моменты:

  • SecurityContextHolder по умолчанию использует ThreadLocal, поэтому контекст изолирован для каждого потока (запроса).
  • После обработки запроса SecurityContextPersistenceFilter очищает ThreadLocal, чтобы избежать утечек памяти.
  • Для REST API (статус-лесс) контекст не сохраняется в сессии между запросами. Он воссоздается при каждом запросе на основе токена (JWT).