← Назад к вопросам

Опиши путь запроса от отправки до получения токена

2.0 Middle🔥 151 комментариев
#Docker, Kubernetes и DevOps#JVM и управление памятью#ORM и Hibernate

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Путь запроса: от отправки до получения JWT токена

Это классический вопрос об аутентификации. Расскажу подробно о полном цикле обработки запроса на основе моего опыта с Spring Security и OAuth2/JWT.

1. Клиент отправляет учётные данные

// Клиент (FrontEnd или мобильное приложение)
POST /api/v1/auth/login
Content-Type: application/json

{
    "email": "user@example.com",
    "password": "securePassword123"
}

2. Запрос попадает в Filter Chain

Spring Security имеет цепочку фильтров, которая обрабатывает каждый запрос:

// Цепочка фильтров срабатывает ДО попадания в Controller

SecurityFilterChain → RequestMappingHandlerMapping → Controller
      ↓
   Filters:
   - SecurityContextPersistenceFilter
   - HeaderWriterFilter
   - LogoutFilter
   - UsernamePasswordAuthenticationFilter  // ← Точка входа для логина
   - CorsFilter
   - CsrfFilter
   - ExceptionTranslationFilter
   - AuthorizationFilter

3. Обработка в контроллере аутентификации

@RestController
@RequestMapping("/api/v1/auth")
public class AuthController {
    
    private final AuthenticationManager authManager;
    private final JwtTokenProvider jwtProvider;
    private final UserService userService;
    
    @PostMapping("/login")
    public ResponseEntity<LoginResponse> login(@RequestBody LoginRequest request) {
        try {
            // Шаг 1: Валидация входных данных
            validateLoginRequest(request);
            
            // Шаг 2: Создание объекта Authentication для обработки
            UsernamePasswordAuthenticationToken token = 
                new UsernamePasswordAuthenticationToken(
                    request.getEmail(),
                    request.getPassword()
                );
            
            // Шаг 3: Передача в AuthenticationManager
            Authentication authenticatedToken = authManager.authenticate(token);
            
            // Шаг 4: Получение информации пользователя
            UserDetails userDetails = (UserDetails) authenticatedToken.getPrincipal();
            
            // Шаг 5: Генерация JWT токена
            String jwtToken = jwtProvider.generateToken(userDetails);
            
            // Шаг 6: Возврат ответа клиенту
            return ResponseEntity.ok(new LoginResponse(jwtToken, "Bearer"));
            
        } catch (BadCredentialsException e) {
            throw new UnauthorizedException("Invalid email or password");
        }
    }
}

4. Процесс аутентификации (AuthenticationManager)

@Configuration
@EnableWebSecurity
public class SecurityConfig {
    
    @Bean
    public AuthenticationManager authenticationManager(
            UserDetailsService userDetailsService,
            PasswordEncoder passwordEncoder) {
        
        return new ProviderManager(
            new DaoAuthenticationProvider() {{
                setUserDetailsService(userDetailsService);
                setPasswordEncoder(passwordEncoder);
            }}
        );
    }
}

// UserDetailsService реализация
@Service
public class CustomUserDetailsService implements UserDetailsService {
    
    private final UserRepository userRepository;
    private final PasswordEncoder passwordEncoder;
    
    @Override
    public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
        User user = userRepository.findByEmail(email)
            .orElseThrow(() -> new UsernameNotFoundException("User not found"));
        
        return org.springframework.security.core.userdetails.User.builder()
            .username(user.getEmail())
            .password(user.getPasswordHash())  // уже захеширован
            .authorities(user.getRoles())      // ROLE_USER, ROLE_ADMIN
            .accountNonExpired(true)
            .accountNonLocked(!user.isLocked())
            .credentialsNonExpired(true)
            .enabled(user.isActive())
            .build();
    }
}

5. Генерация JWT токена

@Component
public class JwtTokenProvider {
    
    @Value("${jwt.secret}")
    private String jwtSecret;
    
    @Value("${jwt.expiration:86400000}")  // 24 часа в миллисекундах
    private long jwtExpirationInMs;
    
    public String generateToken(UserDetails userDetails) {
        Date now = new Date();
        Date expiryDate = new Date(now.getTime() + jwtExpirationInMs);
        
        return Jwts.builder()
            .setSubject(userDetails.getUsername())  // email
            .claim("roles", userDetails.getAuthorities())
            .claim("userId", getUserId(userDetails))  // Дополнительные данные
            .setIssuedAt(now)
            .setExpiration(expiryDate)
            .signWith(SignatureAlgorithm.HS512, jwtSecret)
            .compact();
    }
}

6. Структура JWT токена

JWT Token = Header.Payload.Signature

Header: {"alg": "HS512", "typ": "JWT"}
Payload: {
  "sub": "user@example.com",
  "roles": ["ROLE_USER"],
  "userId": "123e4567-e89b-12d3-a456-426614174000",
  "iat": 1704067200,
  "exp": 1704153600
}
Signature: HMACSHA512(base64(header) + "." + base64(payload), secret)

7. Возврат ответа клиенту

HTTP/1.1 200 OK
Content-Type: application/json
Set-Cookie: jwt=eyJhbGc...; Path=/; HttpOnly; Secure

{
  "token": "eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyQGV4YW1wbGUuY29tIiwicm9sZXMiOlsiUk9MRV9VU0VSIl0sImlhdCI6MTcwNDA2NzIwMCwiZXhwIjoxNzA0MTUzNjAwfQ.qNL_...",
  "tokenType": "Bearer",
  "expiresIn": 86400,
  "user": {
    "id": "123e4567-e89b-12d3-a456-426614174000",
    "email": "user@example.com",
    "roles": ["ROLE_USER"]
  }
}

8. Использование токена в последующих запросах

// Клиент отправляет токен в заголовке
GET /api/v1/users/profile
Authorization: Bearer eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9...

// Spring Security автоматически валидирует токен
@GetMapping("/profile")
@PreAuthorize("hasRole(USER)")
public ResponseEntity<UserProfileDTO> getProfile(
        @AuthenticationPrincipal UserDetails userDetails) {
    return ResponseEntity.ok(userService.getUserProfile(userDetails.getUsername()));
}

9. Жизненный цикл запроса: полная диаграмма

Клиент
  ↓
 HTTP POST /api/v1/auth/login
  ↓
ServletDispatcher
  ↓
SecurityFilterChain
  ├─ SecurityContextPersistenceFilter
  ├─ AuthorizationFilter
  └─ ExceptionTranslationFilter
  ↓
AuthController.login()
  ├─ validateLoginRequest()
  ├─ authManager.authenticate(UsernamePasswordAuthenticationToken)
  │   ├─ DaoAuthenticationProvider
  │   ├─ loadUserByUsername()
  │   ├─ passwordEncoder.matches()
  │   └─ return Authentication with UserDetails
  ├─ jwtProvider.generateToken(UserDetails)
  └─ return LoginResponse with JWT
  ↓
Response (200 OK с токеном)
  ↓
Клиент получает токен и сохраняет его
  ↓
В следующих запросах отправляет в Authorization: Bearer <token>

10. Ключевые моменты для собеседования

Безопасность:

  • Пароль никогда не передаётся открытым текстом (HTTPS)
  • Пароли хешируются (BCrypt, Argon2)
  • JWT подписан и не может быть подделан без секрета
  • HTTPS + Secure флаг на cookie = защита от MITM

Производительность:

  • Валидация токена не требует обращения в БД (всё в JWT)
  • Токен хранится на клиенте, не нужна сессия на сервере
  • Масштабируемость: любой сервер может валидировать токен

Отказ от доступа:

  • Токен с истёкшей датой (exp) будет отклонен
  • Blacklist токенов для logout
  • Refresh tokens для продления сессии