Access Token (токен доступа) — это ключ, который позволяет пользователю взаимодействовать с защищёнными ресурсами. Однако, из соображений безопасности, его время жизни ограничено. Например, токен доступа может быть валидным только в течение 15 минут. После истечения срока действия токена все защищённые запросы начнут возвращать ошибку 401 Unauthorized. Пользователь в этом случае вынужден заново проходить авторизацию, что явно будет вызывать у него недовольство (и вряд ли он поставит вашему приложению 5 звёзд в App Store).
Решение: Refresh токены
Refresh токен предназначен для обновления истёкшего Access Token, не требуя от пользователя повторного ввода учётных данных. Это шаг делает взаимодействие пользователя с приложением плавным, а систему — более удобной.
Как это работает?
- При успешной авторизации сервер выдает два токена:
Access Token— для доступа к защищённым ресурсам.Refresh Token— для получения нового Access Token, когда старый истёк.
- Когда срок действия Access Token истекает, клиент (например, ваш мобильный или веб-приложение) отправляет Refresh Token на сервер.
- Сервер проверяет Refresh Token и, если он действителен, выдает новый Access Token.
- Пользователь продолжает работу без повторной авторизации.
Настройка и использование 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 токенов в своих приложениях и можете существенно улучшить пользовательский опыт и безопасность!
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ