JavaRush /Курсы /Модуль 5. Spring /Лекция 106: Использование Refresh токенов и их настройка

Лекция 106: Использование Refresh токенов и их настройка

Модуль 5. Spring
11 уровень , 5 лекция
Открыта

Access Token (токен доступа) — это ключ, который позволяет пользователю взаимодействовать с защищёнными ресурсами. Однако, из соображений безопасности, его время жизни ограничено. Например, токен доступа может быть валидным только в течение 15 минут. После истечения срока действия токена все защищённые запросы начнут возвращать ошибку 401 Unauthorized. Пользователь в этом случае вынужден заново проходить авторизацию, что явно будет вызывать у него недовольство (и вряд ли он поставит вашему приложению 5 звёзд в App Store).

Решение: Refresh токены

Refresh токен предназначен для обновления истёкшего Access Token, не требуя от пользователя повторного ввода учётных данных. Это шаг делает взаимодействие пользователя с приложением плавным, а систему — более удобной.

Как это работает?

  1. При успешной авторизации сервер выдает два токена:
    • Access Token — для доступа к защищённым ресурсам.
    • Refresh Token — для получения нового Access Token, когда старый истёк.
  2. Когда срок действия Access Token истекает, клиент (например, ваш мобильный или веб-приложение) отправляет Refresh Token на сервер.
  3. Сервер проверяет Refresh Token и, если он действителен, выдает новый Access Token.
  4. Пользователь продолжает работу без повторной авторизации.

Настройка и использование Refresh токенов в Spring

Давайте создадим приложение на Spring Boot, которое будет использовать Refresh токены для обновления Access Token. Для этого потребуется уже существующее приложение с JWT-аутентификацией. Если вы проделывали упражнения из предыдущих лекций, у вас уже есть базовая настройка JWT. Если нет, то рекомендую вернуться немного назад и сделать это.

1. Генерация Refresh токенов

Начнем с изменения логики выдачи токенов. Теперь наш endpoint для аутентификации будет возвращать два токена.

Пример кода генерации токенов:


@RestController
@RequestMapping("/auth")
public class AuthController {

    private final JwtService jwtService; // Сервис для работы с JWT
    private final UserService userService;

    @PostMapping("/login")
    public ResponseEntity
    login(@RequestBody LoginRequest request) {
        // Проверяем учётные данные пользователя
        User user = userService.authenticate(request.getUsername(), request.getPassword());

        // Генерируем Access и Refresh токены
        String accessToken = jwtService.generateAccessToken(user);
        String refreshToken = jwtService.generateRefreshToken(user);

        // Возвращаем их в ответе
        return ResponseEntity.ok(Map.of(
            "accessToken", accessToken,
            "refreshToken", refreshToken
        ));
    }
}

Обратите внимание, что теперь мы используем два разных метода для генерации Access и Refresh токенов. Access Token обычно имеет короткий срок действия (например, 15 минут), тогда как Refresh Token — более продолжительный (например, 7 дней).

2. Добавление endpoint для обновления токенов

Теперь мы добавим endpoint, который принимает Refresh Token и возвращает новый Access Token.

Пример реализации:


@RestController
@RequestMapping("/auth")
public class AuthController {

    private final JwtService jwtService;

    @PostMapping("/refresh")
    public ResponseEntity<?> refresh(@RequestBody Map<String, String> body) {
        String refreshToken = body.get("refreshToken");

        // Проверяем Refresh токен
        if (!jwtService.isValidRefreshToken(refreshToken)) {
            throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "Invalid Refresh Token");
        }

        // Генерируем новый Access Token
        String newAccessToken = jwtService.generateAccessTokenFromRefreshToken(refreshToken);

        return ResponseEntity.ok(Map.of(
            "accessToken", newAccessToken
        ));
    }
}

3. Логика проверки и генерации токенов в JwtService

Мы добавим два новых метода в JwtService. Один для проверки Refresh токена, а другой для генерации нового Access Token на его основе.

Пример:


@Service
public class JwtService {

    private final String secretKey = "your-secret-key";  // Секретный ключ для подписи токенов

    public boolean isValidRefreshToken(String refreshToken) {
        try {
            Claims claims = Jwts.parser()
                .setSigningKey(secretKey)
                .parseClaimsJws(refreshToken)
                .getBody();

            // Убедитесь, что это действительно Refresh токен
            String tokenType = claims.get("type", String.class);
            return "refresh".equals(tokenType);
        } catch (JwtException e) {
            return false;
        }
    }

    public String generateAccessTokenFromRefreshToken(String refreshToken) {
        Claims claims = Jwts.parser()
            .setSigningKey(secretKey)
            .parseClaimsJws(refreshToken)
            .getBody();

        String username = claims.getSubject();

        // Создаём новый Access Token
        return Jwts.builder()
            .setSubject(username)
            .setIssuedAt(new Date())
            .setExpiration(new Date(System.currentTimeMillis() + 15 * 60 * 1000)) // 15 минут
            .signWith(SignatureAlgorithm.HS512, secretKey)
            .compact();
    }
}

Здесь важно убедиться, что Refresh токен действительно является Refresh токеном (например, по дополнительному полю типа в Payload).

4. Безопасное обращение с Refresh токенами

Refresh токены более уязвимы, поскольку имеют длительное время жизни. Вот несколько рекомендаций для повышения безопасности:

  • Хранение на клиенте: никогда не храните Refresh токены в localStorage. Используйте HTTP-only cookies.
  • Протокол HTTPS: всегда используйте защищённое соединение при передаче токенов.
  • Ротация токенов: после использования Refresh токена для обновления Access Token, вы можете сгенерировать новый Refresh Token и вернуть его клиенту. Это уменьшает вероятность компрометации.
  • Список отозванных токенов: в случае обнаружения утечки токена, вы должны иметь возможность отзывать Refresh токен. Обычно это делается с использованием базы данных для отслеживания действительных токенов.

5. Обработка ошибок

Типичные проблемы с Refresh токенами:

  • Истёкший токен: верните 401 Unauthorized и попросите пользователя пройти авторизацию снова.
  • Невалидный токен: если токен подпорчен, откажитесь от его обработки.
  • Подделка токена: используйте цифровую подпись (HMAC или RSA) для проверки подлинности.

Пример обработки ошибок:


if (!jwtService.isValidRefreshToken(refreshToken)) {
    throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "Invalid Refresh Token");
}

Практическое применение

Сейчас уже сложно представить современное приложение без Refresh токенов. Например:

  • В мобильных приложениях, где важно поддерживать пользователя залогиненным длительное время.
  • В веб-приложениях, которые должны поддерживать фоновые запросы (например, получение новой ленты новостей).
  • Работодатели ценят знание Refresh токенов, так как это демонстрирует понимание принципов безопасности, пользовательского опыта и работы с токенами.

Официальная документация Spring Security по работе с JWT и OAuth2 доступна здесь.

Теперь вы готовы к использованию Refresh токенов в своих приложениях и можете существенно улучшить пользовательский опыт и безопасность!

Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ