2012-11-26 11 views
14

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.

risposta

8

È importante ricordare che l'ordine dei filtri in Spring Security è importante.

Da Spring Security 3 libro:

Il ExceptionTranslationFilter sarà in grado di gestire e reagire solo le eccezioni generate sottostante nella pila catena dei filtri . Gli utenti spesso si confondono, specialmente quando aggiungono i filtri personalizzati nell'ordine errato, perché il comportamento previsto differisce dall'effettiva gestione delle eccezioni dell'applicazione - in molti casi di , l'ordine dei filtri è la colpa!

Se i filtri riguardano l'autorizzazione, è buona norma metterli al termine della catena poiché questo approccio viene utilizzato dai filtri di autorizzazione predefiniti. In questo modo non devi reinventare la ruota.

filtri standard: Table in documentation

Dopo aver configurato correttamente il vostro catena di filtri, è possibile configurare pagina di errore, o addirittura gestore personalizzato. Ulteriori informazioni disponibili su documentation.

+1

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

+0

Sì. Qualsiasi posizione dopo "EXCEPTION_TRANSLATION_FILTER" è buona. –

+0

Questo in combinazione con un gestore personalizzato farà il trucco. Grazie! – David

0

Vedo che ExceptionTranslationFilter gestisce solo due eccezioni AuthenticationException e AccessDeniedException con gestori personalizzati per queste due eccezioni, per quanto riguarda qualsiasi altro tipo di eccezione o anche eccezioni di esecuzione?

Come gestiresti/intercetterai quasi tutte le eccezioni nello stack di filtri Spring? Non esiste un modo standard Spring per catturare e ottenere una richiesta (oltre a scrivere un filtro personalizzato sopra tutto), una risposta senza scrivere un altro filtro sopra tutto?

<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> 

Beh, ho finito per l'aggiunta di un altro filtro a destra in alto (o configurare il filtro per/* in web.xml) che aveva semplicemente provare blocco catch e delegato qualsiasi eccezioni non rilevate a un componente primavera gestore di eccezioni a chiamata un metodo ExceptionController (ogni metodo restituisce un tipo di risposta diverso in modi diversi) in modo sicuro, restituendo anche messaggi di eccezioni personalizzate in base al tipo di eccezione (il nostro requisito). L'unica nota negativa è stata l'aggiunta di una logica in modo da non continuare il ciclo. L'eccezione Spring ExceptionHandlerExceptionResolver e @ExceptionHandler nei controller non gestiscono le eccezioni dei filtri e hanno limitazioni su come si desidera restituire un messaggio di eccezione come (XML/JSON, reindirizzamento, inoltro, ....). Questo presuppone che tu abbia una buona gerarchia di Eccezione delle applicazioni che cattura le eccezioni e le lancia con informazioni di riferimento sensibili poiché i filtri non hanno nulla.

Uguale ai codici di errore, definire pagine statiche in web.xml ma catturarle associando un filtro al dispatcher ERROR e preparando il modello per le pagine che visualizzano il codice di errore.