Spring Security: дополнение JWT токенов Refresh Token и метод-уровневая безопасность
Реализация JWT-аутентификации в Spring Security часто ограничивается базовым выпуском и проверкой токенов. Однако без дополнительных механизмов, таких как Refresh Token и метод-уровневая безопасность, система остаётся уязвимой или неудобной для пользователей. В этой статье мы разберём, как добавить

Реализация JWT-аутентификации в Spring Security часто ограничивается базовым выпуском и проверкой токенов. Однако без дополнительных механизмов, таких как Refresh Token и метод-уровневая безопасность, система остаётся уязвимой или неудобной для пользователей. В этой статье мы разберём, как добавить эти важные компоненты, а также реализовать проверку «свой/чужой профиль», чтобы сделать приложение более надёжным и безопасным.
Зачем нужен Refresh Token и как его реализовать
JWT-токены обычно имеют короткое время жизни — от 15 до 30 минут. Это снижает риск при утечке, но заставляет пользователя часто входить заново. Refresh Token решает эту проблему: это долгоживущий токен (например, 7 дней), который хранится на клиенте и позволяет бесшовно обновлять основной JWT. При этом Refresh Token должен быть защищён: его хранят в базе данных, связывая с пользователем, и при каждом обновлении выполняют ротацию — старый токен инвалидируется, а новый выдаётся. Это предотвращает кражу сессии.
Реализация на Spring Security включает создание эндпоинта /refresh, фильтр для проверки Refresh Token и сервис для управления ими. Важно настроить корректное хранение хеша Refresh Token в БД, а не самого токена в открытом виде. Например, можно использовать таблицу refreshtokens с полями: id, userid, tokenhash, expiresat, revoked. При запросе на обновление сервер проверяет хеш, срок действия и флаг отзыва, после чего выдаёт новый JWT и новый Refresh Token.
Как правильно хранить Refresh Token на клиенте?
На клиенте Refresh Token рекомендуется хранить в HttpOnly и Secure куках, чтобы минимизировать риск XSS-атак. Access Token можно хранить в памяти приложения или в localStorage с осторожностью. При каждом запросе фильтр JwtAuthenticationFilter извлекает токен из заголовка Authorization, проверяет подпись и срок, устанавливает SecurityContext. Refresh Token отправляется только на эндпоинт обновления, что снижает поверхность атаки.
Метод-уровневая безопасность: @PreAuthorize и кастомные аннотации
Вместо того чтобы проверять права в каждом контроллере, Spring Security позволяет аннотировать методы с помощью @PreAuthorize и SpEL-выражений. Например, @PreAuthorize("hasRole('ADMIN')") или @PreAuthorize("@securityService.isOwner(userId)"). Для этого нужно включить глобальную метод-уровневую безопасность через @EnableGlobalMethodSecurity(prePostEnabled = true). Такой подход делает код чище и безопаснее: проверки централизованы, а не размазаны по контроллерам.
Можно создавать собственные аннотации, например @CurrentUser, чтобы получать текущего аутентифицированного пользователя в параметрах метода. Это упрощает доступ к данным пользователя без явного вызова SecurityContext. Кастомные аннотации также позволяют инкапсулировать сложную логику проверки прав, делая код более читаемым.
Как работает правило «свой/чужой профиль»?
Часто требуется, чтобы пользователь мог редактировать только свой профиль, а администратор — любой. Реализовать это можно через кастомный Security Service, который проверяет, совпадает ли ID из JWT с ID запрашиваемого профиля или у пользователя есть роль ADMIN. В контроллере используется @PreAuthorize("@profileSecurity.isOwnerOrAdmin(userId)"), где profileSecurity — бин с методом isOwnerOrAdmin. Этот метод получает текущего пользователя из SecurityContext и сравнивает ID. Если пользователь не авторизован или ID не совпадают — выбрасывается исключение, которое обрабатывается глобальным обработчиком.
Технические детали реализации
Автор использует стандартный стек: Spring Boot, Spring Security, JWT (библиотека jjwt), PostgreSQL. Ключевые моменты: - Access Token хранится в памяти клиента (localStorage или куки с флагом HttpOnly). Refresh Token — в куках с HttpOnly и Secure. - При каждом запросе фильтр JwtAuthenticationFilter извлекает токен из заголовка Authorization, проверяет подпись и срок, устанавливает SecurityContext. - Refresh Token хранится в таблице refreshtokens с полями: id, userid, tokenhash, expiresat, revoked. - При ротации старый Refresh Token помечается как revoked, а новый создаётся и сохраняется. - Для метод-уровневой безопасности используется @EnableGlobalMethodSecurity(prePostEnabled = true) и кастомный MethodSecurityExpressionHandler, если нужна сложная логика.
Кого затронет и как
Разработчики, использующие Spring Security, получат готовые шаблоны для типовых задач. В первую очередь это актуально для проектов с микросервисной архитектурой, где JWT — основной способ аутентификации. Безопасность повышается за счёт ротации Refresh Token и централизованных проверок прав. В российских компаниях, где часто используют Spring, эти практики помогут соответствовать требованиям по защите персональных данных (152-ФЗ). Например, хранение Refresh Token в БД с хешированием — это дополнительный уровень безопасности.
Что будет дальше
Автор планирует расширить пример: добавить логирование действий с токенами, реализовать отзыв всех Refresh Token пользователя (при смене пароля) и интеграцию с OAuth2. В комментариях к статье уже обсуждают возможность использования Redis для хранения Refresh Token вместо БД — это ускорит проверку. Также вероятно появление готовой библиотеки-стартера, которая включает все описанные механики «из коробки». Сообщество Spring активно развивает Security, и подобные дополнения могут войти в официальные рекомендации.
Итог
Дополнение JWT-аутентификации Refresh Token и метод-уровневой безопасностью делает приложение на Spring Security более надёжным и удобным. Эти техники закрывают частые уязвимости и упрощают разработку. Если вы работаете с Spring, стоит внедрить их уже сейчас — это сэкономит время в будущем.