Access Token (токен доступу) — це ключ, який дозволяє користувачу взаємодіяти з захищеними ресурсами. Однак, з міркувань безпеки, його час життя обмежений. Наприклад, 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 токенів у своїх додатках і можете суттєво покращити користувацький досвід і безпеку!
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ