Как Spring Security сопоставляет обращение на сервер с клиентом
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
# Как Spring Security сопоставляет обращение на сервер с клиентом
Это важный аспект безопасности веб-приложений. Spring Security использует несколько механизмов для аутентификации и отслеживания клиента через несколько запросов.
Основная задача
HTTP — это stateless протокол. Каждый запрос независим от предыдущих. Но нам нужно знать, кто это (аутентификация) и оставаться в системе между запросами (session).
Механизм 1: Session + Cookies
Это самый распространённый способ:
Первый запрос (логин):
1. Клиент отправляет username и password
2. Сервер аутентифицирует
3. Сервер создаёт Session в памяти (обычно в SessionRepository)
4. Сервер возвращает JSESSIONID cookie клиенту
Последующие запросы:
1. Браузер автоматически отправляет JSESSIONID cookie
2. Сервер находит Session по ID
3. Сервер извлекает аутентифицированного пользователя из Session
В Spring Security:
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.sessionManagement()
.sessionFixationProtection(SessionFixationProtectionStrategy.MIGRATEDSESSION)
.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED);
return http.build();
}
}
Механизм 2: Authentication Object
В контексте текущего запроса Spring Security хранит информацию о пользователе в объекте Authentication:
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth != null && auth.isAuthenticated()) {
String username = auth.getName();
Collection<? extends GrantedAuthority> authorities = auth.getAuthorities();
}
Механизм 3: SecurityContext + ThreadLocal
Spring Security использует ThreadLocal для хранения контекста безопасности в рамках одного запроса:
Поток 1 (запрос 1)
├── SecurityContext = User A, Role ADMIN
├── UserController.getCurrentUser() -> User A
└── После завершения запроса -> очистка ThreadLocal
Поток 2 (запрос 2, другой клиент)
├── SecurityContext = User B, Role USER
├── UserController.getCurrentUser() -> User B
└── После завершения запроса -> очистка ThreadLocal
// В контроллере
@RestController
public class UserController {
@GetMapping("/me")
public User getCurrentUser() {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
return (User) auth.getPrincipal();
}
}
Механизм 4: JWT (JSON Web Token) — для API
Для stateless API используют JWT:
Первый запрос (логин):
1. Клиент отправляет credentials
2. Сервер проверяет и создаёт JWT (содержит: user id, roles, expiry)
3. Сервер возвращает JWT клиенту
Последующие запросы:
1. Клиент отправляет JWT в Authorization header
2. Сервер парсит и валидирует JWT (подпись, expiry)
3. Сервер создаёт Authentication object из JWT
@Configuration
@EnableWebSecurity
public class JwtSecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS) // Никаких сессий!
.and()
.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
return http.build();
}
@Bean
public JwtAuthenticationFilter jwtAuthenticationFilter() {
return new JwtAuthenticationFilter();
}
}
public class JwtAuthenticationFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain chain) {
String token = extractJwtFromRequest(req);
if (token != null && validateJwt(token)) {
String userId = getUserIdFromJwt(token);
// Создаём Authentication для этого запроса
Authentication auth = new UsernamePasswordAuthenticationToken(
userId, null, getAuthoritiesFromJwt(token)
);
SecurityContextHolder.getContext().setAuthentication(auth);
}
chain.doFilter(req, res);
}
}
Механизм 5: Distributed Sessions
Для масштабируемых систем с несколькими серверами используют централизованное хранилище сессий:
Клиент -> Балансировщик нагрузки
├-> Сервер 1
├-> Сервер 2
└-> Сервер 3
Все серверы смотрят в одно место (Redis, MongoDB) за Session
@Configuration
@EnableSpringHttpSession
public class HttpSessionConfig {
@Bean
public LettuceConnectionFactory connectionFactory() {
return new LettuceConnectionFactory();
}
}
Фильтры Spring Security
Spring Security использует цепочку фильтров для обработки каждого запроса:
FilterChain:
1. ExceptionTranslationFilter
2. FilterSecurityInterceptor
3. UsernamePasswordAuthenticationFilter (обработка логина)
4. JwtAuthenticationFilter (валидация JWT)
5. AnonymousAuthenticationFilter (анонимный пользователь)
...
Каждый фильтр проверяет запрос и либо пропускает его дальше, либо блокирует.
Практический пример: Session-based
@RestController
public class AuthController {
private UserRepository userRepository;
@PostMapping("/login")
public ResponseEntity<?> login(@RequestBody LoginRequest req, HttpSession session) {
User user = userRepository.findByUsername(req.getUsername());
if (user != null && passwordEncoder.matches(req.getPassword(), user.getPassword())) {
// Spring Security автоматически создаст сессию
Authentication auth = new UsernamePasswordAuthenticationToken(
user, null, user.getAuthorities()
);
SecurityContextHolder.getContext().setAuthentication(auth);
return ResponseEntity.ok("Logged in");
}
return ResponseEntity.status(401).body("Invalid credentials");
}
@GetMapping("/protected")
public String protectedResource() {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
User user = (User) auth.getPrincipal();
return "Hello, " + user.getUsername();
}
}
Вывод
Spring Security сопоставляет запросы с клиентами через:
- Session + Cookies — для традиционных веб-приложений
- SecurityContext + ThreadLocal — для текущего запроса
- JWT — для stateless API
- Distributed Sessions — для масштабируемых систем
- Фильтры — для перехвата и валидации каждого запроса
Выбор метода зависит от архитектуры приложения и требований к масштабируемости.