Під час вивчення веб-застосунків ти напевно зустрічався з терміном "сесія". Сесія — це механізм, який дозволяє серверу "пам'ятати" користувача між запитами. Це критично важливо для авторизації: інакше довелося б вводити логін і пароль при кожному кліку.
Як працює сесія? Коли користувач аутентифікується, сервер створює унікальний ідентифікатор (Session ID). Цей ідентифікатор зазвичай відправляється клієнту (наприклад, браузеру) у вигляді cookie. При кожному наступному запиті клієнт прикріплює цей ідентифікатор, що дозволяє серверу зрозуміти, хто це.
Управління сесіями в Spring Security
Spring Security включає потужний функціонал для роботи з сесіями. Давай подивимось, як налаштувати сесії та керувати ними.
Налаштування політики сесій
Одне з перших завдань, з яким ти зіткнешся, — це налаштування політики управління сесіями. Наприклад, можна визначити, скільки активних сесій може бути в одного користувача. Для цього використовуються методи sessionManagement() і maximumSessions().
Приклад:
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.sessionManagement()
.maximumSessions(1) // Тільки одна сесія на користувача
.maxSessionsPreventsLogin(true); // Заборонити вхід при перевищенні ліміту
}
}
Тут ми обмежили користувача однією активною сесією. Якщо він спробує зайти з іншого пристрою, йому відмовлять у вході.
Обробка завершення сесії
Spring Security дозволяє налаштувати логіку дій при завершенні сесії (наприклад, якщо сесія скінчилася або була завершена вручну). Для цього можна використовувати інтерфейс SessionRegistry.
Обробник завершення сесії може виглядати так:
@Component
public class CustomLogoutHandler implements LogoutHandler {
@Override
public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
System.out.println("Сесія завершена: " + authentication.getName());
}
}
Зберігання сесій
Сесії можуть зберігатися або в пам'яті сервера, або в базі даних. Використання бази даних поширене в розподілених системах, де балансувальник навантаження може направляти користувача на різні сервери між запитами. У таких випадках сесії мають бути "спільними".
Токени безпеки: чому сесій недостатньо?
В епоху мікросервісів і REST API концепція токенів виходить на передній план. Токени — це компактні рядки, які представляють певну інформацію (наприклад, ідентифікатор користувача). На відміну від сесій, токени не вимагають зберігання на стороні сервера.
Переваги токенів
- Незалежність від стану сервера: токени зберігаються на клієнті, що спрощує масштабування застосунків.
- Зниження навантаження: серверу не потрібно зберігати стан для кожного користувача.
- Зручність для API: токени можна відправляти в заголовку HTTP-запиту, що ідеально для REST API.
JWT (JSON Web Token)
JWT — це популярний формат токенів, який представляє собою закодований JSON-рядок. Кожен токен складається з трьох частин:
- Header (заголовок): тип токена і використовуваний алгоритм шифрування.
- Payload (корисне навантаження): дані токена, наприклад
userIdабоrole. - Signature (підпис): захищає токен від підробки.
Приклад JWT токена:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VySWQiLCJyb2xlIjoiVVNFUiJ9.abc123signature
Використання JWT в Spring Security
Додамо JWT у безпеку нашого застосунку.
Для генерації токенів тобі знадобиться бібліотека, наприклад, io.jsonwebtoken (JWT). Ось приклад створення токена:
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.util.Date;
public class JwtTokenUtil {
private static final String SECRET_KEY = "mySecretKey";
public String generateToken(String username) {
return Jwts.builder()
.setSubject(username)
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + 86400000)) // 1 день
.signWith(SignatureAlgorithm.HS256, SECRET_KEY)
.compact();
}
}
Перевірка токенів
Перш ніж використовувати токен, важливо переконатися, що він справжній і не прострочений. Для цього потрібно декодувати і перевірити підпис токена:
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.Claims;
public class JwtTokenUtil {
private static final String SECRET_KEY = "mySecretKey";
public Claims validateToken(String token) {
return Jwts.parser()
.setSigningKey(SECRET_KEY)
.parseClaimsJws(token)
.getBody();
}
}
Метод validateToken() повертає корисне навантаження токена, якщо він дійсний.
Налаштування JWT-аутентифікації
Тепер додамо інтеграцію JWT в Spring Security. Для цього створимо фільтр:
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
@Autowired
private JwtTokenUtil jwtTokenUtil;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
String token = request.getHeader("Authorization");
if (token != null && token.startsWith("Bearer ")) {
token = token.substring(7); // Видаляємо "Bearer "
Claims claims = jwtTokenUtil.validateToken(token);
if (claims != null) {
String username = claims.getSubject();
// Створюємо об'єкт аутентифікації
UsernamePasswordAuthenticationToken auth =
new UsernamePasswordAuthenticationToken(username, null, Collections.emptyList());
SecurityContextHolder.getContext().setAuthentication(auth);
}
}
filterChain.doFilter(request, response);
}
}
Фільтр перевіряє токен перед тим, як запит буде переданий далі.
Приклад використання токенів у нашому застосунку
Ми створили REST API для аутентифікації. У контролері /login повертається JWT-токен. Клієнт має надсилати цей токен в заголовку Authorization у наступних запитах.
Контролер:
@RestController
public class AuthController {
@Autowired
private JwtTokenUtil jwtTokenUtil;
@PostMapping("/login")
public ResponseEntity
login(@RequestParam String username, @RequestParam String password) {
// Тут ти зазвичай перевіряєш username/password
String token = jwtTokenUtil.generateToken(username);
return ResponseEntity.ok(new HashMap<String, String>() {{
put("token", token);
}});
}
}
Тепер твій клієнт може зробити авторизований запит:
curl -H "Authorization: Bearer <your_token>" http://localhost:8080/protected/resource
Коли використовувати сесії, а коли — токени?
Якщо ти працюєш з традиційними веб-застосунками, сесії все ще відмінний вибір. Вони прості, зручні і добре інтегровані в екосистему Spring.
Однак для REST API і мікросервісів сесії не такі зручні. Тут на передній план виходять токени, особливо JWT, які пропонують мобільність, легкість масштабування і незалежність від стану сервера.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ