УРОК 10 / 30 0%

Spring Security basics

🎯

Цель урока

Сделать полноценный CRUD для задач с правильными HTTP-статусами (201/204/404), обработкой ошибок, и подготовить Task к подключению JPA.

🧠

Теория · для собеса

1 Архитектура Spring Security

Основа — цепочка фильтров (Filter Chain). Каждый HTTP-запрос проходит через ~15 фильтров ДО Controller'а:


HTTP request
  ↓
[SecurityContextHolderFilter]    ← создаёт SecurityContext
  ↓
[CsrfFilter]                     ← проверяет CSRF-токен
  ↓
[UsernamePasswordAuthenticationFilter]  ← логин по form
  ↓
[BearerTokenAuthenticationFilter]       ← JWT (если есть)
  ↓
[AuthorizationFilter]            ← проверяет @PreAuthorize, hasRole
  ↓
[Controller]
2 Ключевые понятия
  • Authentication (кто ты?) — Principal (юзер), Credentials (пароль/токен), Authorities (роли)
  • Authorization (что тебе можно?) — проверка прав на действие
  • SecurityContext — хранит Authentication для текущего запроса (через ThreadLocal)
  • UserDetails — Spring-интерфейс для юзера (имя, пароль, authorities)
  • UserDetailsService — загрузка юзера по имени
  • PasswordEncoder — хеширование пароля (BCrypt)
3 Что нового в Spring Security 6
  • ❌ Удалён WebSecurityConfigurerAdapter (deprecated с 5.7)
  • ✅ Вместо него — SecurityFilterChain бин
  • ✅ Конфигурация через лямбды (функциональный стиль)
  • ✅ Встроенный AuthenticationProvider для OAuth 2 / JWT

---

💻

Практика: улучшаем CRUD

  1. 1
    Зависимости
    
    dependencies {
        implementation("org.springframework.boot:spring-boot-starter-security")
    }
    
  2. 2
    config/SecurityConfig.java (новый стиль)
    
    package com.taskflow.config;
    
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.security.config.annotation.web.builders.HttpSecurity;
    import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
    import org.springframework.security.config.http.SessionCreationPolicy;
    import org.springframework.security.core.userdetails.User;
    import org.springframework.security.core.userdetails.UserDetails;
    import org.springframework.security.core.userdetails.UserDetailsService;
    import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
    import org.springframework.security.crypto.password.PasswordEncoder;
    import org.springframework.security.provisioning.InMemoryUserDetailsManager;
    import org.springframework.security.web.SecurityFilterChain;
    import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
    
    @Configuration
    @EnableWebSecurity
    public class SecurityConfig {
    
        @Bean
        public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
            http
                // Отключаем CSRF — мы используем JWT (stateless), не cookies+forms
                .csrf(csrf -> csrf.disable())
    
                // Настройка прав
                .authorizeHttpRequests(auth -> auth
                    // Public endpoints
                    .requestMatchers("/api/auth/**", "/api/health", "/v3/api-docs/**", "/swagger-ui/**").permitAll()
                    // Admin only
                    .requestMatchers("/api/admin/**").hasRole("ADMIN")
                    // Остальные — только аутентифицированные
                    .anyRequest().authenticated()
                )
    
                // Stateless — не создаём HTTP-сессию
                .sessionManagement(s -> s.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
    
                // Basic Auth — для теста (в уроке 12 заменим на JWT)
                .httpBasic(b -> {})
    
                // Form login — отключаем
                .formLogin(f -> f.disable())
    
                // Logout — настраиваем
                .logout(l -> l
                    .logoutUrl("/api/auth/logout")
                    .logoutSuccessHandler((req, res, auth) -> res.setStatus(200))
                );
    
            return http.build();
        }
    
        // Пока — in-memory юзер, в уроке 11 заменим на JPA
        @Bean
        public UserDetailsService userDetailsService(PasswordEncoder encoder) {
            UserDetails user = User.builder()
                .username("alice")
                .password(encoder.encode("password123"))
                .roles("USER")
                .build();
    
            UserDetails admin = User.builder()
                .username("admin")
                .password(encoder.encode("admin123"))
                .roles("USER", "ADMIN")
                .build();
    
            return new InMemoryUserDetailsManager(user, admin);
        }
    
        @Bean
        public PasswordEncoder passwordEncoder() {
            return new BCryptPasswordEncoder(12);
        }
    }
    
  3. 3
    Тест: запускаем и проверяем
    
    ./gradlew bootRun
    
    
    # Public endpoint — без авторизации
    $ curl http://localhost:8080/api/health
    {"status":"UP","version":"0.1.0","timestamp":"2026-06-29T16:40:00Z"}
    
    # Защищённый endpoint — 401 без токена
    $ curl -i http://localhost:8080/api/tasks
    HTTP/1.1 401
    
    # С Basic Auth — работает
    $ curl -u alice:password123 http://localhost:8080/api/tasks
    []
    
    # Admin endpoint — 403 для USER
    $ curl -u alice:password123 http://localhost:8080/api/admin/dashboard
    HTTP/1.1 403
    
    # Admin endpoint — 200 для ADMIN
    $ curl -u admin:admin123 http://localhost:8080/api/admin/dashboard
    {"adminsOnly":true}
    
  4. 4
    Логирование в Spring Security (для дебага)

    application-dev.yml:

    
    logging:
      level:
        org.springframework.security: DEBUG
        org.springframework.security.web.FilterChainProxy: TRACE
    

    ---

🎯

Зачем это на собесе

После урока ты должен уметь ответить на:

Самый частый вопрос на собесе: «Как работает Spring Security?». Теперь ты знаешь цепочку фильтров.
Stateless + JWT — стандарт для REST API. HTTP-сессии не масштабируются на несколько инстансов.
CSRF отключён — безопасно ТОЛЬКО для stateless API с JWT. Если есть cookie-based auth — CSRF нужен.
BCrypt — стандарт хеширования. strength=12 — баланс между скоростью и безопасностью (2^12 = 4096 итераций).

5 вопросов на углубление

Раскрой вопрос и нажми «🤔 Хочу разобрать подробнее» — он попадёт в страницу ответов.

1
Что включает Spring Security по умолчанию?

Разбор внутри: дефолтный user, basic/form login и почему нужен свой SecurityFilterChain.

2
Что значит strength у BCryptPasswordEncoder?

Разбор внутри: log2 rounds, цена хеширования и баланс скорости/безопасности.

3
CSRF: когда отключать, а когда нельзя?

Разбор внутри: JWT в Authorization header против cookie-based login.

4
hasRole vs hasAuthority — в чём отличие?

Разбор внутри: ROLE_ prefix, роли, permissions и scopes.

5
STATELESS vs IF_REQUIRED — как выбрать session policy?

Разбор внутри: REST/JWT и классическое web-приложение с сессией.

Готов идти дальше?

Выбери вопросы, которые тебе интересны, и изучи их. Потом — к следующему уроку.

🚀 Перейти к Уроку 11

Сначала пройди все секции и выбери хотя бы 1 вопрос

⬅️ Назад к Уроку 9
🎉
Новый тир
Новый тир достигнут!