Отладка контекста безопасности — Spring Security в деталях

При работе с контекстом безопасности Spring Security иногда возникают ситуации, когда разрабатываемое приложение начинает себя вести не очевидно, отвечать ошибками с HTTP-статусом 403, но без каких-либо подробностей, в том числе и в логах. В рамках этой статьи предлагаю разобраться с тем, как можно добиться большей конкретики от Spring Security при возникновении подобных ситуаций.

Логгирование

Первый способ — изменение уровня детализации логов для классов пакета org.springframework.security на TRACE. Как правило, этого будет вполне достаточно для того, чтобы отследить большинство ошибок, возникающих при работе со Spring Security.

Пример лога при ошибке валидации CSRF-токена:

Как видно, обработка запроса была остановлена на CSRF-фильтре.

До версии Spring Security 6.1 достаточно было уровня DEBUG, начиная с 6.1 требуется TRACE для получения подробного лога, впрочем на уровне DEBUG вывод был бы следующим:

Данный способ должен быть приоритетным, так как для его использования не требуется ни внесение изменений в код, ни даже перезапуск сервиса — при помощи инструментов администрирования уровень логгирования можно поменять на ходу. Кроме того данный способ можно считать безопасным, так как в логах выводится только отладочная информация.

Включение режима отладки

Второй способ повысить информативность логов Spring Security — включить режим отладки. Делается это при помощи параметра debug аннотации @EnableWebSecurity:

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

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

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

В целом данный способ предоставляет достаточно подробную информацию о запросе, но требует сборки и перезапуска сервиса.

Обработка ошибок

Два основных специфичных для Spring Security типа исключений: AuthenticationException, возникающее в процессе аутентификации, и AccessDeniedException, возникающее в случае отсутствие у пользователя доступа к запрашиваемому ресурсу.

Исключения AuthenticationException обрабатывается точками входа — AuthenticationEntryPoint, а AccessDeniedException — компонентами AccessDeniedHandler.

Чтобы залоггировать подробности этих исключений, нужно добавить соответствующие компоненты в настройки цепочки фильтров безопасности при помощи DSL exceptionHandling:

Приведённый пример кода позволяет получить информацию о возникших исключениях, но использовать его в этом виде я рекомендую только в процессе разработки, когда предыдущие два способа не дали результата. Всё потому, что пользователь в случае с такими обработчиками увидит пустую страницу с HTTP-статусом 200 OK. Если же вы хотите логгировать эти ошибки во время реальной эксплуатации сервиса, то лучше задекорировать стандартные обработчики ошибок

Данные обработчики используются не только фильтром ExceptionTranslationFilter, который используется для перехвата исключений, выбрасываемых другими фильтрами и сервлетом, но и другими фильтрами для корректного ответа пользователям в случае ошибок.

Например CsrfFilter использует указанный accessDeniedHandler в случае, если в запросе, требующем CSRF-токен, токен отсутствует или невалиден.

Как и предыдущий способ отладки контекста безопасности, этот способ требует сборки и перезапуска сервиса, но может дать более детальную информацию о возникающих ошибках.

Фильтр для обработки ошибок

Упомянутый чуть выше фильтр ExceptionTranslationFilter обрабатывает только исключения, специфичные для Spring Security, но кроме них в процессе обработки HTTP-запроса могут возникать и другие исключения. И если у вас есть желание логгировать эти исключения в рамках цепочки фильтров безопасности, то вы можете создать свой фильтр, логгирующий возникающие исключения:

И зарегистрировать его в цепочке фильтров безопасности в самом её начале:

Впрочем, особого смысла в этом фильтре нет, так как исключения, вылетающие за пределы цепочки фильтров безопасности, в любом случае будут залоггированы другими компонентами.

Понравилась статья? Тогда поддержки проект и подкинь монетку:

Больше полезных статей и роликов: