java - mediante - Maneje excepciones personalizadas en Spring Security
spring framework pdf español (2)
Es importante recordar que el orden de los filtros en Spring Security importa.
Del libro Spring Security 3 :
El
ExceptionTranslationFilter
será capaz de manejar y reaccionar solo ante aquellas excepciones que se lanzan debajo de él en la pila de ejecución de la cadena de filtros. Los usuarios a menudo se confunden, especialmente cuando agregan filtros personalizados en el orden incorrecto, en cuanto a por qué el comportamiento esperado difiere del manejo de excepciones real de la aplicación; en muchos de estos casos, el culpable es el orden de los filtros.
Si sus filtros tratan sobre la autorización, es una buena práctica ponerlos al final de la cadena ya que este enfoque es utilizado por los filtros de autorización predeterminados. De esa manera no tienes que reinventar la rueda.
Filtros estándar: tabla en la documentación
Después de configurar correctamente la cadena de filtros, puede configurar la página de error o incluso el controlador personalizado. Más información disponible en la documentación .
Estamos creando una API RESTful con Spring MVC + spring security + hibernate. La API puede producir JSON y HTML. Hacer un buen manejo de errores para la seguridad de primavera me está dando dolor de cabeza:
La autenticación puede ocurrir de varias maneras: BasicAuth, a través de diferentes parámetros en una solicitud POST y también a través de la web de inicio de sesión. Para cada mecanismo de autenticación, se ha declarado un filtro en el elemento de espacio de nombres <http>
de spring security xml config.
Manejamos todas nuestras excepciones de primavera en un HandlerExceptionResolver
personalizado. Esto funciona bien para todas las excepciones lanzadas en nuestros controladores, pero no sé cómo manejar las excepciones personalizadas lanzadas en los filtros de seguridad de primavera personalizados. Como el filtro de seguridad de primavera viene antes de invocar cualquiera de nuestros controladores, no vemos excepciones que arrojemos en nuestros filtros de seguridad de primavera personalizados.
Encontré esta pregunta aquí en stackoverflow: use excepciones personalizadas en Spring Security . Sin embargo, no entiendo dónde manejan las excepciones que se lanzan allí. Probamos este enfoque pero no se llama a nuestro HandlerExceptionResolver
personalizado. En cambio, al usuario se le presenta una fea stacktrace representada por tomcat.
¿Porqué necesitamos esto? Los usuarios pueden ser activados y desactivados. Si están desactivados e intentan realizar ciertas acciones, nos gustaría devolver JSON con un mensaje de error personalizado. Esto debería ser diferente de lo que se muestra cuando la seguridad de primavera arroja una AccessDeniedException
. AccessDeniedException
alguna manera llega a nuestro HandlerExceptionResolver
, pero no pude seguir exactamente.
Posible solución Pensamos en usar un ExceptionTranslationFilter
, sin embargo, esto no se llama cuando arrojamos nuestras excepciones personalizadas (establecer un punto de interrupción en la declaración catch del método doFilter ()). Según entiendo, este bloque catch debe ser llamado y se debe usar un punto de entrada de autenticación.
Otra posibilidad: podríamos hacer algo similar al ExceptionTranslationFilter
en la cadena de filtros de seguridad de primavera y hacer algo similar a lo que hace su AccessDeniedHandler
:
RequestDispatcher dispatcher = request.getRequestDispatcher(errorPage);
dispatcher.forward(request, response);
Podríamos agregar algunos parámetros (código de error, razón, etc.) a la solicitud y hacer que apunte a un controlador que se encargaría de la representación en JSON o HTML.
Aquí hay un breve extracto de nuestra configuración:
Seguridad de primavera:
<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 del HandlerExceptionResolver
@Bean
public HandlerExceptionResolver handlerExceptionResolver(){
logger.info("creating handler exception resolver");
return new AllExceptionHandler();
}
Nuestra HandlerExceptionResolver personalizada
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");
}
La parte relevante de uno de nuestros filtros:
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>
¿Alguien tiene alguna sugerencia sobre cómo podemos resolver esto? Miré a través de muchos artículos en google, la mayoría de ellos describen cómo manejar AccessDeniedException arrojado por la seguridad de primavera cuando ningún filtro puede autenticar la solicitud.
Estamos usando Spring Security 3.1.0 y spring web mvc 3.1.0.
Veo que ExceptionTranslationFilter solo maneja dos excepciones AuthenticationException y AccessDeniedException con manejadores personalizados para estas dos excepciones, ¿qué pasa con cualquier otro tipo de excepción o incluso excepciones de tiempo de ejecución?
¿Cómo manejaría / interceptaría casi cualquier excepción en la pila de filtro Spring? ¿No hay alguna forma estándar de Spring para atrapar y obtener solicitudes (además de escribir un filtro personalizado sobre todo), responder sin escribir otro filtro sobre todo?
<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>
Bueno, terminé agregando otro filtro en la parte superior (o configurando el filtro para / * en web.xml) que simplemente había intentado capturar bloque y delegó cualquier excepción no detectada a un manejador de excepciones personalizado. Componente Spring llamando a un método ExceptionController (devolviendo cada método diferente tipo de respuesta de diferentes maneras) de una manera segura también devolviendo mensajes de excepción personalizados basados en el tipo de excepción (nuestro requisito). La única parte negativa fue agregar algo de lógica para que no sigas repitiendo. Spring ExceptionHandlerExceptionResolver y @ExceptionHandler personalizados en los controladores no manejan las excepciones de filtro y tienen limitaciones sobre cómo desea devolver un mensaje de excepción como (XML / JSON, redirigir, reenviar, ...). Esto supone que tiene una buena jerarquía de Excepción de aplicación que detecta excepciones y las arroja con información de referencia sensible ya que los filtros no tienen nada.
Lo mismo para los códigos de error, defina las páginas estáticas en web.xml pero sí las capte asignando un filtro al despachador ERROR y preparando el modelo para las páginas que muestran el código de error.