Как работают фильтры (Filters) в Spring Security и как создавать собственные?

Ответ

В Spring Security фильтры Servlet образуют цепочку, которая обрабатывает каждый HTTP-запрос до того, как он достигнет контроллера. Они отвечают за аутентификацию, авторизацию, защиту от атак и другую сквозную логику.

Ключевые встроенные фильтры:

  • SecurityContextPersistenceFilter: Восстанавливает SecurityContext (информацию об аутентификации) из сессии между запросами.
  • UsernamePasswordAuthenticationFilter: Обрабатывает форму логина (POST /login).
  • BasicAuthenticationFilter: Обрабатывает HTTP Basic Authentication.
  • FilterSecurityInterceptor: Принимает окончательное решение о доступе к защищенному ресурсу на основе конфигурации (authorizeRequests()).

Создание и регистрация кастомного фильтра:

  1. Реализация фильтра: Наследуйтесь от OncePerRequestFilter, чтобы гарантировать однократное выполнение за запрос.
    @Component
    public class CustomLoggingFilter extends OncePerRequestFilter {
        @Override
        protected void doFilterInternal(HttpServletRequest request,
                                        HttpServletResponse response,
                                        FilterChain filterChain) throws ServletException, IOException {
            long startTime = System.currentTimeMillis();
            // Логика ДО прохождения запроса дальше по цепочке
            filterChain.doFilter(request, response); // Пропускаем запрос дальше
            // Логика ПОСЛЕ прохождения цепочки (когда ответ уже сформирован)
            long duration = System.currentTimeMillis() - startTime;
            logger.info("Request {} completed in {} ms", request.getRequestURI(), duration);
        }
    }
  2. Встраивание в цепочку Security: Укажите позицию фильтра относительно других.

    @Configuration
    @EnableWebSecurity
    public class SecurityConfig {
        @Autowired
        private CustomLoggingFilter customLoggingFilter;
    
        @Bean
        public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
            http
                .addFilterBefore(customLoggingFilter, UsernamePasswordAuthenticationFilter.class)
                // .addFilterAfter(filter, existingFilterClass)
                // .addFilterAt(filter, existingFilterClass) // заменяет позицию
                .authorizeRequests()
                // ... остальная конфигурация
            return http.build();
        }
    }

    Почему это важно: Понимание цепочки фильтров позволяет правильно добавлять логирование, проверку заголовков, кастомную аутентификацию (например, по JWT) и другие cross-cutting concerns.

Ответ 18+ 🔞

А, слушай, смотри, вот эта вся ваша Spring Security, она там внутри, блядь, как матрёшка, сука, только из фильтров! Представь себе конвейер, ёпта, на заводе. Запрос приходит — и его по цепочке начинают пинать, как мячик, от одного фильтра к другому, пока он до контроллера не доползёт, весь измученный.

Эти, блядь, главные надзиратели на конвейере:

  • SecurityContextPersistenceFilter: Этот чувак — как охранник на проходной с фотографией. Ты утром пришёл, он тебя по пропуску узнал, и весь день ты по заводу ходишь, и он тебя не трогает. Он из сессии достаёт твой SecurityContext (кто ты и на что имеешь право) и суёт тебе его в карман на каждый запрос.
  • UsernamePasswordAuthenticationFilter: А это, сука, тот самый дядька в отделе кадров, где ты форму заполняешь. POST на /login прилетел — он тебя за шиворот: «Логин-пароль, быстро!». Проверит — и пропустит дальше, уже с бейджиком.
  • BasicAuthenticationFilter: Это если ты не через форму, а сразу с порога орёшь «Я Вася!» и суёшь под нос свою базовую авторизацию в заголовках. Он тебя тоже опознает, но с меньшим энтузиазмом.
  • FilterSecurityInterceptor: Ну а это, блядь, начальник цеха, последняя инстанция. Запрос уже почти у ресурса, а этот выходит: «Стоять! А по какому праву?». Смотрит в свои правила (authorizeRequests()), и если прав нет — пиздык дверью перед носом. До контроллера запрос может так и не дойти, застряв на этой мудозвонской проверке.

Хочешь своего охранника на конвейер поставить? Легко!

  1. Сделай фильтр: Главное — наследуйся от OncePerRequestFilter, чтобы твой охранник не взбесился и не проверял одного и того же чела по десять раз за смену.
    @Component
    public class CustomLoggingFilter extends OncePerRequestFilter {
        @Override
        protected void doFilterInternal(HttpServletRequest request,
                                        HttpServletResponse response,
                                        FilterChain filterChain) throws ServletException, IOException {
            long startTime = System.currentTimeMillis();
            // Тут можешь что-то сделать ДО того, как запрос пойдёт по цепочке дальше
            filterChain.doFilter(request, response); // Это как крикнуть "Следующий!" на конвейере
            // А тут — логика ПОСЛЕ, когда ответ уже почти готов. Идеально, чтобы засечь, сколько всё длилось, ебать.
            long duration = System.currentTimeMillis() - startTime;
            logger.info("Запрос {} обработан за {} мс", request.getRequestURI(), duration);
        }
    }
  2. Встрой его в очередь: А то он встанет не в ту очередь и всех обоссёт. Нужно указать, перед кем или после кого он должен работать.

    @Configuration
    @EnableWebSecurity
    public class SecurityConfig {
        @Autowired
        private CustomLoggingFilter customLoggingFilter;
    
        @Bean
        public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
            http
                // Вот, например, поставим нашего логгера ПЕРЕД тем фильтром, что логины проверяет
                .addFilterBefore(customLoggingFilter, UsernamePasswordAuthenticationFilter.class)
                // Можно и .addFilterAfter (после какого-то), или .addFilterAt (вместо, но осторожно, ёпта!)
                .authorizeRequests()
                // ... ну и дальше твои правила кто куда может
            return http.build();
        }
    }

А нахуя это всё понимать? Да затем, сука, что без этого ты как слепой котёнок! Хочешь логировать все запросы? Добавляй свой фильтр. Нужно JWT-токен из заголовка выковыривать и юзера авторизовывать? Фильтр тебе в помощь. Защита от каких-нибудь ебучих атак? Опять фильтры! Это ж, блядь, фундамент, на котором вся эта security-хуиcurity держится. Не разберёшься — так и будешь тыкаться палкой в конфигурацию, надеясь на авось.