JavaRush /Курсы /Модуль 5. Spring /Обработка сессий и токенов

Обработка сессий и токенов

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

Во время изучения веб-приложений вы наверняка сталкивались с термином "сессия". Сессия — это механизм, который позволяет серверу "помнить" пользователя между запросами. Это критически важно для авторизации: иначе вам пришлось бы вводить логин и пароль при каждом клике.

Как работает сессия? Когда пользователь аутентифицируется, сервер создаёт уникальный идентификатор (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 концепция токенов выходит на первый план. Токены — это компактные строки, которые представляют определённую информацию (например, идентификатор пользователя). В отличие от сессий, токены не требуют хранения на стороне сервера.

Преимущества токенов

  1. Статус независимости сервера: токены хранятся на клиенте, что упрощает масштабирование приложений.
  2. Снижение нагрузки: серверу не нужно хранить состояние для каждого пользователя.
  3. Удобство для API: токены можно отправлять в заголовке HTTP-запроса, что идеально для REST API.

JWT (JSON Web Token)

JWT — это популярный формат токенов, который представляет собой закодированную строку JSON. Каждый токен состоит из трёх частей:

  1. Header (заголовок): тип токена и используемый алгоритм шифрования.
  2. Payload (полезная нагрузка): данные токена, например userId или role.
  3. 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, которые предлагают мобильность, лёгкость масштабирования и независимость от состояния сервера.


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