Подтвердить что ты не робот

Обработка пользовательских исключений в Spring безопасности

Мы создаем RESTful API с spring MVC + spring security + hibernate. API может создавать как JSON, так и HTML. Выполнение хорошей обработки ошибок для безопасности spring дает мне головную боль:

Аутентификация может происходить различными способами: BasicAuth, с помощью различных параметров в запросе POST, а также через веб-вход в систему. Для каждого механизма проверки подлинности существует фильтр, объявленный в элементе пространства <http> пространства имен конфигурации spring безопасности xml.

Мы обрабатываем все наши исключения spring в пользовательском HandlerExceptionResolver. Это отлично подходит для всех исключений, которые были выбраны нашими контроллерами, но я не знаю, как обрабатывать пользовательские исключения, созданные в пользовательских фильтрах безопасности spring. Поскольку фильтр защиты spring появляется до вызова любого из наших контроллеров, мы не видим исключений, которые мы выбрали в наших пользовательских фильтрах безопасности spring.

Я нашел этот вопрос здесь в stackoverflow: Использовать специальные исключения в spring Безопасность. Однако я не понимаю, где они обрабатывают исключения, которые там бросаются. Мы пробовали этот подход, но наш пользовательский HandlerExceptionResolver не вызывается. Вместо этого пользователю предоставляется уродливая stacktrace, предоставленная tomcat.

Зачем нам это нужно? Пользователи могут быть активированы и деактивированы. Если они деактивированы и попытаются выполнить определенные действия, мы хотели бы вернуть JSON с пользовательским сообщением об ошибке. Это должно отличаться от того, что отображается, когда spring security выбрасывает AccessDeniedException. AccessDeniedException каким-то образом превращает его в наш HandlerExceptionResolver, но я не мог точно следовать.

Возможное решение Мы думали об использовании ExceptionTranslationFilter, однако это не вызывается, когда мы бросаем наши пользовательские исключения (устанавливаем точку останова в выводе метода doFilter()). В моем понимании этот блок catch должен быть вызван, и должна использоваться точка входа аутентификации.

Другая возможность: мы могли бы сделать что-то похожее на ExceptionTranslationFilter в цепочке фильтров безопасности spring и сделать что-то похожее на то, что делает его AccessDeniedHandler:

RequestDispatcher dispatcher = request.getRequestDispatcher(errorPage);
dispatcher.forward(request, response);

Мы могли бы добавить в запрос некоторые параметры (код ошибки, причины и т.д.) и указать на контроллер, который позаботится о рендеринге в JSON или HTML.

Вот краткий отрывок нашей конфигурации:

Spring Безопасность:

<http create-session="stateless" use-expressions="true" >
    <!-- Try getting the authorization object from the request parameters. -->
    <security:custom-filter ref="filter1" after="SECURITY_CONTEXT_FILTER"/>
    <security:custom-filter ref="filter2" before="LOGOUT_FILTER"/>
    <!-- Intercept certain URLS differently -->

    <intercept-url pattern="/admin/**" access="hasRole('ROLE_ADMIN')" />
    <!-- Some more stuff here -->
    <intercept-url pattern="/**" access="denyAll" />  
    <http-basic />
</http>

AppConfig для HandlerExceptionResolver

@Bean
public HandlerExceptionResolver handlerExceptionResolver(){
    logger.info("creating handler exception resolver");
    return new AllExceptionHandler();
}

Наш пользовательский HandlerExceptionResolver

public class AllExceptionHandler implements HandlerExceptionResolver {

    private static final Logger logger = LoggerFactory
        .getLogger(AppConfig.class);

    @Override
    public ModelAndView resolveException(HttpServletRequest request,
            HttpServletResponse response, Object handler, Exception ex) {
    // This is just a snipped of the real method code
    return new ModelAndView("errorPage");
}

Соответствующая часть одного из наших фильтров:

try {
    Authentication authResult = authenticationManger.authenticate(authRequest);
    SecurityContextHolder.getContext().setAuthentication(authResult);
}

catch(AuthenticationException failed) {
    SecurityContextHolder.clearContext();
    throw failed; 
}

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
<context-param>
    <param-name>contextClass</param-name>
    <param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
</context-param>
<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>xxx.xxx.xxx.config</param-value>
</context-param>
<context-param>
    <param-name>spring.profiles.default</param-name>
    <param-value>LIVE</param-value>
</context-param>
<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<servlet>
    <servlet-name>appServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value></param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
    <!-- Add multipart support for files up to 10 MB -->
    <multipart-config>
        <max-file-size>10000000</max-file-size>
    </multipart-config>
</servlet>
<servlet-mapping>
    <servlet-name>appServlet</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>
<filter>
    <filter-name>openEntityManagerInViewFilter</filter-name>
    <filter-class>org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter</filter-class>
</filter>

<filter-mapping>
    <filter-name>openEntityManagerInViewFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>
<filter>
    <filter-name>springSecurityFilterChain</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<!-- Map filters -->
<filter-mapping>
    <filter-name>springSecurityFilterChain</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>
<error-page>
    <error-code>404</error-code>
    <location>/handle/404</location>
</error-page>
</web-app>

Есть ли у кого-нибудь указания относительно того, как мы можем это решить? Я просмотрел много статей о google, большинство из них описывают, как обрабатывать AccessDeniedException, созданное защитой spring, когда ни один фильтр не может аутентифицировать запрос.

Мы используем spring Security 3.1.0 и spring web mvc 3.1.0.

4b9b3361

Ответ 1

Важно помнить, что порядок фильтров в Spring имеет значение.

Из Spring Безопасность 3 книга:

ExceptionTranslationFilter сможет обрабатывать и реагировать на только те исключения, которые выбрасываются под ним в цепочке фильтров исполняемый стек. Пользователи часто путаются, особенно при добавлении пользовательские фильтры в неправильном порядке, относительно того, почему ожидаемое поведение отличается от их применения фактической обработкой исключений - во многих из эти случаи, порядок фильтров виноват!

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

Стандартные фильтры: Таблица в документации

После того, как вы правильно настроили цепочку фильтров, вы можете настроить страницу с ошибкой или даже пользовательский обработчик. Более подробная информация доступна в документации.

Ответ 2

Я вижу, что ExceptionTranslationFilter обрабатывает только два исключения: AuthenticationException и AccessDeniedException с настраиваемыми обработчиками для этих двух исключений, что касается любого другого исключения исключения или даже исключения времени выполнения?

Как бы вы обрабатывали/перехватывали практически любое исключение в стеке Spring? Нет ли Spring стандартного способа уловить и получить запрос (помимо написания настраиваемого фильтра поверх всего), ответ, не записывая еще один фильтр поверх всего?

<security:http auto-config="false" use-expressions="true"
disable-url-rewriting="true" entry-point-ref="authenticationEntryPoint"
pattern="/**">

<security:custom-filter before="FIRST" ref="stackExceptionFilter" />

<security:custom-filter before="..." ref="authenticationFilter" />
<security:logout />
</security:http> 

Ну, я включил еще один фильтр прямо (или настроил фильтр для /* в web.xml), который просто попробовал блок catch и делегировал любое неперехваченное исключение для специального обработчика исключений Spring, вызывающего ExceptionController метод (каждый метод, возвращающий разные типы ответов по-разному) в безопасном режиме, также возвращающий пользовательские сообщения исключения на основе типа исключения (наше требование). Единственной нижней частью было добавление некоторой логики, поэтому вы не будете продолжать цикл. Spring пользовательские ExceptionHandlerExceptionResolver и @ExceptionHandler в контроллерах не обрабатывают исключения фильтров и имеют ограничение на то, как вы хотите вернуть сообщение об исключении как (XML/JSON, перенаправление, переадресация,....). Это предполагает, что у вас есть хорошая иерархия исключений приложений, которая ловит исключения и бросает их с разумной справочной информацией, поскольку фильтры ничего не имеют.

То же самое для кодов ошибок, задайте статические страницы в web.xml, но поймайте их, сопоставив фильтр с диспетчером ERROR и подготовьте модель для страниц, отображающих код ошибки.