Мы создаем 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.