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

Как просиходит алгоритм аутентификации и авторизации через JWT token в Spring Security

2.0 Middle🔥 171 комментариев
#REST API и микросервисы#Spring Boot и Spring Data#Безопасность

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

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

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

# Аутентификация и авторизация через JWT в Spring Security

Краткий ответ

Spring Security с JWT работает по следующему алгоритму:

  1. Аутентификация: пользователь отправляет учетные данные, сервер проверяет их и выдает JWT токен
  2. Хранение токена: клиент сохраняет токен (обычно в localStorage/sessionStorage)
  3. Авторизация: при каждом запросе клиент отправляет токен в заголовке Authorization
  4. Валидация токена: Spring Security проверяет подпись токена и извлекает данные пользователя
  5. Выдача доступа: если токен валиден, запрос обрабатывается с правами пользователя

Что такое JWT (JSON Web Token)?

JWT состоит из трех частей, разделенных точками:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

1. Header (заголовок)

{
  "alg": "HS256",
  "typ": "JWT"
}

Содержит информацию об алгоритме шифрования и типе токена.

2. Payload (полезная нагрузка)

{
  "sub": "user123",
  "email": "user@example.com",
  "roles": ["ADMIN", "USER"],
  "iat": 1516239022,
  "exp": 1516242622
}

Содержит данные о пользователе и стандартные claims:

  • sub (subject): ID пользователя
  • email: электронная почта
  • roles: роли пользователя
  • iat (issued at): время выдачи
  • exp (expiration): время истечения

3. Signature (подпись)

HMACSHA256(base64(header) + "." + base64(payload), secret)

Подпись подтверждает, что токен не был изменен.

Полный процесс в Spring Security

Этап 1: Аутентификация (вход в систему)

@RestController
@RequestMapping("/api/auth")
public class AuthController {
    @Autowired
    private AuthenticationManager authenticationManager;
    
    @Autowired
    private JwtTokenProvider jwtTokenProvider;
    
    @PostMapping("/login")
    public ResponseEntity<?> login(@RequestBody LoginRequest loginRequest) {
        // 1. Создание объекта аутентификации
        Authentication authentication = authenticationManager.authenticate(
            new UsernamePasswordAuthenticationToken(
                loginRequest.getUsername(),
                loginRequest.getPassword()
            )
        );
        
        // 2. Spring Security проверяет учетные данные
        //    (вызывает UserDetailsService.loadUserByUsername())
        
        // 3. Установка контекста безопасности
        SecurityContextHolder.getContext().setAuthentication(authentication);
        
        // 4. Генерация JWT токена
        String jwt = jwtTokenProvider.generateToken(authentication);
        
        // 5. Возврат токена клиенту
        return ResponseEntity.ok(new JwtAuthenticationResponse(jwt));
    }
}

Этап 2: Генерация JWT токена

@Component
public class JwtTokenProvider {
    @Value("${app.jwtSecret}")
    private String jwtSecret;
    
    @Value("${app.jwtExpirationInMs}")
    private int jwtExpirationInMs;
    
    public String generateToken(Authentication authentication) {
        UserPrincipal userPrincipal = (UserPrincipal) authentication.getPrincipal();
        
        Date now = new Date();
        Date expiryDate = new Date(now.getTime() + jwtExpirationInMs);
        
        // 1. Создание payload
        return Jwts.builder()
            .setSubject(String.valueOf(userPrincipal.getId()))
            .claim("email", userPrincipal.getEmail())
            .claim("roles", userPrincipal.getAuthorities())
            .setIssuedAt(now)
            .setExpiration(expiryDate)
            // 2. Подпись токена с использованием secret key
            .signWith(SignatureAlgorithm.HS512, jwtSecret)
            .compact();
    }
}

Этап 3: Хранение токена на клиенте

// JavaScript / React
const response = await fetch('/api/auth/login', {
    method: 'POST',
    body: JSON.stringify({ username: 'user', password: 'pass' })
});

const data = await response.json();
const token = data.token;

// Сохранение в localStorage
localStorage.setItem('jwtToken', token);

// Отправка в следующих запросах
fetch('/api/users/profile', {
    headers: {
        'Authorization': `Bearer ${token}`
    }
});

Этап 4: Валидация токена (на сервере)

public class JwtAuthenticationFilter extends OncePerRequestFilter {
    @Autowired
    private JwtTokenProvider tokenProvider;
    
    @Override
    protected void doFilterInternal(
        HttpServletRequest request,
        HttpServletResponse response,
        FilterChain filterChain) throws ServletException, IOException {
        
        try {
            // 1. Извлечение токена из заголовка
            String jwt = getJwtFromRequest(request);
            
            if (jwt != null && tokenProvider.validateToken(jwt)) {
                // 2. Получение ID пользователя из токена
                Long userId = tokenProvider.getUserIdFromToken(jwt);
                
                // 3. Загрузка пользователя из БД
                UserDetails userDetails = userDetailsService.loadUserById(userId);
                
                // 4. Создание объекта Authentication
                Authentication authentication = new UsernamePasswordAuthenticationToken(
                    userDetails,
                    null,
                    userDetails.getAuthorities()
                );
                
                // 5. Установка контекста безопасности
                SecurityContextHolder.getContext().setAuthentication(authentication);
            }
        } catch (Exception ex) {
            logger.error("Could not set user authentication", ex);
        }
        
        filterChain.doFilter(request, response);
    }
    
    private String getJwtFromRequest(HttpServletRequest request) {
        String bearerToken = request.getHeader("Authorization");
        if (bearerToken != null && bearerToken.startsWith("Bearer ")) {
            return bearerToken.substring(7); // "Bearer " = 7 символов
        }
        return null;
    }
}

Валидация токена

public boolean validateToken(String token) {
    try {
        // 1. Проверка подписи
        Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(token);
        return true;
    } catch (ExpiredJwtException ex) {
        logger.error("Expired JWT token: {}", ex);
    } catch (UnsupportedJwtException ex) {
        logger.error("Unsupported JWT token: {}", ex);
    } catch (MalformedJwtException ex) {
        logger.error("Invalid JWT token: {}", ex);
    } catch (SignatureException ex) {
        logger.error("Invalid JWT signature: {}", ex);
    } catch (IllegalArgumentException ex) {
        logger.error("JWT claims string is empty: {}", ex);
    }
    return false;
}

public Long getUserIdFromToken(String token) {
    Claims claims = Jwts.parser()
        .setSigningKey(jwtSecret)
        .parseClaimsJws(token)
        .getBody();
    
    return Long.parseLong(claims.getSubject());
}

Этап 5: Регистрация фильтра в конфигурации Security

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
    @Bean
    public JwtAuthenticationFilter jwtAuthenticationFilter() {
        return new JwtAuthenticationFilter();
    }
    
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .csrf().disable()
            .exceptionHandling()
                .authenticationEntryPoint(new JwtAuthenticationEntryPoint())
                .and()
            .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
            .authorizeRequests()
                .antMatchers("/api/auth/**").permitAll()
                .antMatchers("/api/admin/**").hasRole("ADMIN")
                .anyRequest().authenticated()
                .and()
            // Добавление фильтра перед стандартным UsernamePasswordAuthenticationFilter
            .addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
    }
}

Полный цикл запроса

1. Клиент → POST /api/auth/login с username и password

2. Server (AuthController):
   - Проверка учетных данных через AuthenticationManager
   - Если верно → генерация JWT
   - Возврат JWT клиенту

3. Клиент сохраняет JWT в localStorage

4. Клиент → GET /api/users/profile с заголовком Authorization: Bearer <JWT>

5. Server (JwtAuthenticationFilter):
   - Извлечение токена из заголовка
   - Валидация подписи (проверка secret)
   - Проверка срока действия
   - Извлечение данных пользователя из payload
   - Создание SecurityContext с правами пользователя

6. Server (бизнес-логика):
   - Обработка запроса с полными правами пользователя
   - Возврат данных

7. Клиент получает ответ с данными

Преимущества JWT

  • Stateless: серверу не нужно хранить сессии
  • Масштабируемость: работает с несколькими серверами без синхронизации
  • Мобильные приложения: удобно передается в заголовках
  • Стандарт: широко поддерживается
  • CORS-friendly: можно использовать с фронтом на другом домене

Недостатки JWT

  • Размер: больше, чем обычная сессия
  • Невозможность отзыва: токен действует до истечения (нужен черный список)
  • Безопасность: secret должен быть тайным и 32+ символа

Пример конфигурации в application.yml

app:
  jwtSecret: "your-256-bit-secret-key-must-be-very-secret-and-long"
  jwtExpirationInMs: 3600000  # 1 час

Вывод

JWT в Spring Security — это мощный механизм для создания масштабируемых API. Алгоритм основан на выдаче криптографически подписанного токена, который клиент отправляет с каждым запросом. Сервер валидирует подпись без необходимости доступа к БД для каждого запроса, что делает систему очень производительной.

Как просиходит алгоритм аутентификации и авторизации через JWT token в Spring Security | PrepBro