Stiamo creando un'API RESTful con Spring MVC + spring security + hibernate. L'API può produrre sia JSON che HTML. Fare una buona gestione degli errori per la sicurezza di primavera mi dà fastidio:Gestisci eccezioni personalizzate in Spring Security
L'autenticazione può avvenire in vari modi: BasicAuth, tramite diversi parametri in una richiesta POST e anche tramite il log-in web. Per ciascun meccanismo di autenticazione, è stato dichiarato un filtro nell'elemento dello spazio dei nomi <http>
della sicurezza xml di configurazione molla.
Gestiamo tutte le nostre eccezioni di primavera in una personalizzata HandlerExceptionResolver
. Funziona bene per tutte le eccezioni generate nei nostri controller, ma non so come gestire le eccezioni personalizzate generate nei filtri di sicurezza personalizzati. Poiché il filtro di sicurezza molla viene prima che venga richiamato uno dei nostri controllori, non vediamo eccezioni che gettiamo nei nostri filtri di sicurezza personalizzati.
Ho trovato questa domanda qui su stackoverflow: Use custom exceptions in Spring Security. Tuttavia non capisco dove gestiscono le eccezioni che vengono lanciate lì. Abbiamo provato questo approccio ma la nostra custom HandlerExceptionResolver
non è stata chiamata. Invece all'utente viene presentato un brutto stacktrace reso da tomcat.
Perché ne abbiamo bisogno? Gli utenti possono essere attivati e disattivati. Se sono disattivati e cercano di eseguire determinate azioni, vorremmo restituire a JSON un messaggio di errore personalizzato. Questo dovrebbe essere diverso da quello che viene visualizzato quando la sicurezza di primavera lancia un AccessDeniedException
. Il AccessDeniedException
in qualche modo lo rende al nostro HandlerExceptionResolver
, ma non ho potuto seguire esattamente come.
Possibile soluzione Abbiamo pensato di usare un ExceptionTranslationFilter
, tuttavia questo non viene chiamato quando gettiamo le nostre eccezioni personalizzate (impostare un punto di interruzione nella dichiarazione cattura del metodo doFilter()). A mio avviso, questo blocco catch dovrebbe essere chiamato e dovrebbe essere usato un punto di ingresso per l'autenticazione.
Altra possibilità: Potremmo fare qualcosa di simile al ExceptionTranslationFilter
nella catena dei filtri di sicurezza a molla e fare qualcosa di simile a quello che il suo AccessDeniedHandler
fa:
RequestDispatcher dispatcher = request.getRequestDispatcher(errorPage);
dispatcher.forward(request, response);
Potremmo aggiungere alcuni parametri (codice di errore, motivo ecc) alla richiesta e fare in modo che punti a un controller che si occupi del rendering in JSON o HTML.
Ecco un breve estratto della nostra configurazione:
Primavera di sicurezza:
<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();
}
nostro HandlerExceptionResolver personalizzato
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 rilevante di uno dei nostri filtri:
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>
Qualcuno ha qualche indicazioni su come potremmo risolvere questo problema? Ho esaminato molti articoli su google, la maggior parte di essi descrive come gestire l'AccessDeniedException generata dalla sicurezza di primavera quando nessun filtro è in grado di autenticare la richiesta.
Stiamo utilizzando Spring Security 3.1.0 e spring web mvc 3.1.0.
Grazie per la risposta: giusto per essere sicuro di aver capito bene. Suggerisci di usare qualcosa come after = "EXCEPTION_TRANSLATION_FILTER" per il mio filtro. – David
Sì. Qualsiasi posizione dopo "EXCEPTION_TRANSLATION_FILTER" è buona. –
Questo in combinazione con un gestore personalizzato farà il trucco. Grazie! – David