Отладка контекста безопасности - Spring Security в деталях
При работе с контекстом безопасности Spring Security иногда возникают ситуации, когда разрабатываемое приложение начинает себя вести не очевидно, отвечать ошибками с HTTP-статусом 403, но без каких-либо подробностей, в том числе и в логах. В рамках этой статьи предлагаю разобраться с тем, как можно добиться большей конкретики от Spring Security при возникновении подобных ситуаций.
Логгирование
Первый способ - изменение уровня детализации логов для классов пакета org.springframework.security на TRACE. Как правило, этого будет вполне достаточно для того, чтобы отследить большинство ошибок, возникающих при работе со Spring Security.
Пример лога при ошибке валидации CSRF-токена:
DEBUG FilterChainProxy : Securing POST /test TRACE FilterChainProxy : Invoking DisableEncodeUrlFilter (1/13) TRACE FilterChainProxy : Invoking WebAsyncManagerIntegrationFilter (2/13) TRACE FilterChainProxy : Invoking SecurityContextHolderFilter (3/13) TRACE FilterChainProxy : Invoking HeaderWriterFilter (4/13) TRACE FilterChainProxy : Invoking CsrfFilter (5/13) DEBUG CsrfFilter : Invalid CSRF token found for http://localhost:8080/test DEBUG AccessDeniedHandlerImpl : Responding with 403 status code
Как видно, обработка запроса была остановлена на CSRF-фильтре.
До версии Spring Security 6.1 достаточно было уровня DEBUG, начиная с 6.1 требуется TRACE для получения подробного лога, впрочем на уровне DEBUG вывод был бы следующим:
DEBUG FilterChainProxy : Securing POST /test DEBUG CsrfFilter : Invalid CSRF token found for http://localhost:8080/test DEBUG AccessDeniedHandlerImpl : Responding with 403 status code
Данный способ должен быть приоритетным, так как для его использования не требуется ни внесение изменений в код, ни даже перезапуск сервиса - при помощи инструментов администрирования уровень логгирования можно поменять на ходу. Кроме того данный способ можно считать безопасным, так как в логах выводится только отладочная информация.
Включение режима отладки
Второй способ повысить информативность логов Spring Security - включить режим отладки. Делается это при помощи параметра debug аннотации @EnableWebSecurity:
@EnableWebSecurity(debug = true)
@Configuration
class SecurityConfiguration {
// компоненты безопасности
}Данный способ можно считать небезопасным, так как в логах может выводиться, в том числе, секретная информация, о чём Spring Security предупреждает вас при запуске приложения в режиме отладки контекста безопасности:
******************************************************************** ********** Security debugging is enabled. ************* ********** This may include sensitive information. ************* ********** Do not use in a production system! ************* ********************************************************************
В данном режиме при обработке каждого запроса выводится достаточно подробная информация о запросе и списке фильтров безопасности, которые будут его обрабатывать:
Request received for POST '/test': org.apache.catalina.connector.RequestFacade@3f3220e4 servletPath:/test pathInfo:null headers: host: localhost:8080 user-agent: curl/7.81.0 accept: */* Security filter chain: [ DisableEncodeUrlFilter WebAsyncManagerIntegrationFilter SecurityContextHolderFilter HeaderWriterFilter CsrfFilter LogoutFilter BasicAuthenticationFilter RequestCacheAwareFilter SecurityContextHolderAwareRequestFilter AnonymousAuthenticationFilter ExceptionTranslationFilter AuthorizationFilter ]
Ниже будет выведен стек вызова, по которому можно отследить, на каком фильтре остановилась обработка запроса.
В целом данный способ предоставляет достаточно подробную информацию о запросе, но требует сборки и перезапуска сервиса.
Обработка ошибок
Два основных специфичных для Spring Security типа исключений: AuthenticationException, возникающее в процессе аутентификации, и AccessDeniedException, возникающее в случае отсутствие у пользователя доступа к запрашиваемому ресурсу.
Исключения AuthenticationException обрабатывается точками входа - AuthenticationEntryPoint, а AccessDeniedException - компонентами AccessDeniedHandler.
Чтобы залоггировать подробности этих исключений, нужно добавить соответствующие компоненты в настройки цепочки фильтров безопасности при помощи DSL exceptionHandling:
@Configuration
class SecurityConfiguration {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http)
throws Exception {
return http
.exceptionHandling(exceptionHandling ->
exceptionHandling
.accessDeniedHandler((request, response, e) ->
// Логгировние ошибок доступа
LOGGER.error(e.getMessage(), e))
.authenticationEntryPoint((request, response, e) ->
// Логгировние ошибок аутентификации
LOGGER.error(e.getMessage(), e)))
.build();
}
}Приведённый пример кода позволяет получить информацию о возникших исключениях, но использовать его в этом виде я рекомендую только в процессе разработки, когда предыдущие два способа не дали результата. Всё потому, что пользователь в случае с такими обработчиками увидит пустую страницу с HTTP-статусом 200 OK. Если же вы хотите логгировать эти ошибки во время реальной эксплуатации сервиса, то лучше задекорировать стандартные обработчики ошибок
Данные обработчики используются не только фильтром ExceptionTranslationFilter, который используется для перехвата исключений, выбрасываемых другими фильтрами и сервлетом, но и другими фильтрами для корректного ответа пользователям в случае ошибок.
Например CsrfFilter использует указанный accessDeniedHandler в случае, если в запросе, требующем CSRF-токен, токен отсутствует или невалиден.
Как и предыдущий способ отладки контекста безопасности, этот способ требует сборки и перезапуска сервиса, но может дать более детальную информацию о возникающих ошибках.
Фильтр для обработки ошибок
Упомянутый чуть выше фильтр ExceptionTranslationFilter обрабатывает только исключения, специфичные для Spring Security, но кроме них в процессе обработки HTTP-запроса могут возникать и другие исключения. И если у вас есть желание логгировать эти исключения в рамках цепочки фильтров безопасности, то вы можете создать свой фильтр, логгирующий возникающие исключения:
class ExceptionLoggingFilter extends OncePerRequestFilter {
private static final Logger LOGGER =
LoggerFactory.getLogger(ExceptionLoggingFilter.class);
@Override
protected void doFilterInternal(
HttpServletRequest req, HttpServletResponse res, FilterChain chain)
throws ServletException, IOException {
try {
chain.doFilter(req, res);
} catch (ServletException | IOException exception) {
LOGGER.error(exception.getMessage(), exception);
throw exception;
}
}
}И зарегистрировать его в цепочке фильтров безопасности в самом её начале:
@Configuration
class SecurityConfiguration {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http)
throws Exception {
return http
.addFilterBefore(new ExceptionLoggingFilter(),
DisableEncodeUrlFilter.class)
.build();
}
}Впрочем, особого смысла в этом фильтре нет, так как исключения, вылетающие за пределы цепочки фильтров безопасности, в любом случае будут залоггированы другими компонентами.
Понравилась статья? Тогда поддержки проект и подкинь монетку:
Больше полезных статей и роликов:
