it-swarm-eu.dev

Come funziona la catena di filtri Spring Security

Mi rendo conto che la sicurezza di Spring si basa su una catena di filtri che intercettano la richiesta, rilevano (assenza di) autenticazione, reindirizzano al punto di ingresso dell'autenticazione o passano la richiesta al servizio di autorizzazione e alla fine lasciano che la richiesta colpisca il servlet o generino un'eccezione di sicurezza (non autenticato o non autorizzato). DelegatingFitlerProxy unisce questi filtri insieme. Per eseguire i loro compiti, questi servizi di accesso al filtro come UserDetailsService e AuthenticationManager .

I filtri chiave della catena sono (nell'ordine)

  • SecurityContextPersistenceFilter (ripristina l'autenticazione da JSESSIONID)
  • UsernamePasswordAuthenticationFilter (esegue l'autenticazione)
  • ExceptionTranslationFilter (cattura le eccezioni di sicurezza da FilterSecurityInterceptor)
  • FilterSecurityInterceptor (può generare eccezioni di autenticazione e autorizzazione)

Sono confuso da come questi filtri vengono utilizzati. È che per la sorgente fornita form-login, UsernamePasswordAuthenticationFilter è usato solo per /login , e gli ultimi filtri no? L'elemento dello spazio dei nomi form-login autoconfigura questi filtri? Ogni richiesta (autenticata o meno) raggiunge FilterSecurityInterceptor per URL non di accesso?

Cosa succede se voglio proteggere la mia REST API con JWT-token , che viene recuperato dal login? Devo configurare due variabili http tag, i diritti? Altro per /login con UsernamePasswordAuthenticationFilter, e un altro per REST url, con JwtAuthenticationFilter personalizzato.

La configurazione di due elementi http crea due springSecurityFitlerChains? UsernamePasswordAuthenticationFilter è disattivato per impostazione predefinita fino a quando non dichiaro form-login? Come sostituire SecurityContextPersistenceFilter con uno, che otterrà Authentication da JWT-token esistente anziché JSESSIONID?

87
Tuomas Toivonen

La catena di filtri di sicurezza Spring è un motore molto complesso e flessibile.

I filtri chiave della catena sono (nell'ordine)

  • SecurityContextPersistenceFilter (ripristina l'autenticazione da JSESSIONID)
  • UsernamePasswordAuthenticationFilter (esegue l'autenticazione)
  • ExceptionTranslationFilter (cattura le eccezioni di sicurezza da FilterSecurityInterceptor)
  • FilterSecurityInterceptor (può generare eccezioni di autenticazione e autorizzazione)

Guardando la versione corrente della documentazione 4.2.1 , sezione 13.3 Ordinamento dei filtri puoi vedere l'intera organizzazione del filtro della catena di filtri:

13.3 Ordinamento dei filtri

L'ordine in cui i filtri sono definiti nella catena è molto importante. Indipendentemente da quali filtri stai effettivamente utilizzando, l'ordine dovrebbe essere il seguente:

  1. ChannelProcessingFilter , perché potrebbe dover reindirizzare a un protocollo diverso

  2. SecurityContextPersistenceFilter , quindi un SecurityContext può essere impostato in SecurityContextHolder all'inizio di una richiesta Web e qualsiasi modifica a SecurityContext può essere copiato su HttpSession quando termina la richiesta web (pronto per l'uso con la prossima richiesta web)

  3. ConcurrentSessionFilter , poiché utilizza la funzionalità SecurityContextHolder e deve aggiornare SessionRegistry per riflettere le richieste in corso dall'unità principale

  4. Meccanismi di elaborazione dell'autenticazione - UsernamePasswordAuthenticationFilter , CasAuthenticationFilter , BasicAuthenticationFilter etc - in modo che SecurityContextHolder possa essere modificato per contenere un token di richiesta di autenticazione valido

  5. Il SecurityContextHolderAwareRequestFilter , se lo si sta utilizzando per installare HttpServletRequestWrapper di Spring Security nel contenitore del servlet

  6. Il JaasApiIntegrationFilter , se un JaasAuthenticationToken è in SecurityContextHolder questo elaborerà il FilterChain come Oggetto in JaasAuthenticationToken

  7. RememberMeAuthenticationFilter , in modo tale che se nessun meccanismo di elaborazione dell'autenticazione precedente aggiornasse SecurityContextHolder e la richiesta presenti un cookie che abiliti i servizi remember-me posto, un oggetto di Autenticazione ricordato adatto verrà messo lì

  8. AnonymousAuthenticationFilter , in modo tale che se nessun meccanismo di elaborazione dell'autenticazione precedente aggiorna SecurityContextHolder, verrà inserito un oggetto di autenticazione anonimo

  9. ExceptionTranslationFilter , per rilevare eventuali eccezioni di Spring Security in modo che sia possibile restituire una risposta di errore HTTP o avviare AuthenticationEntryPoint appropriato

  10. FilterSecurityInterceptor , per proteggere gli URI Web e aumentare le eccezioni quando l'accesso è negato

Ora, proverò ad andare avanti con le tue domande una ad una:

Sono confuso da come questi filtri vengono utilizzati. È che per la primavera fornita da login-form, UsernamePasswordAuthenticationFilter viene usato solo per/login, e gli ultimi filtri no? L'elemento dello spazio dei nomi login form-configura automaticamente questi filtri? Ogni richiesta (autenticata o meno) raggiunge FilterSecurityInterceptor per URL non di accesso?

Una volta che si sta configurando una sezione <security-http>, per ognuno di essi è necessario fornire almeno un meccanismo di autenticazione. Questo deve essere uno dei filtri che corrispondono al gruppo 4 nella sezione 13.3 Ordinamento dei filtri dalla documentazione Spring Security a cui ho appena fatto riferimento.

Questa è la sicurezza minima valida: elemento http che può essere configurato:

<security:http authentication-manager-ref="mainAuthenticationManager" 
               entry-point-ref="serviceAccessDeniedHandler">
    <security:intercept-url pattern="/sectest/zone1/**" access="hasRole('ROLE_ADMIN')"/>
</security:http>

Facendolo, questi filtri sono configurati nel proxy della catena di filtri:

{
        "1": "org.springframework.security.web.context.SecurityContextPersistenceFilter",
        "2": "org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter",
        "3": "org.springframework.security.web.header.HeaderWriterFilter",
        "4": "org.springframework.security.web.csrf.CsrfFilter",
        "5": "org.springframework.security.web.savedrequest.RequestCacheAwareFilter",
        "6": "org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter",
        "7": "org.springframework.security.web.authentication.AnonymousAuthenticationFilter",
        "8": "org.springframework.security.web.session.SessionManagementFilter",
        "9": "org.springframework.security.web.access.ExceptionTranslationFilter",
        "10": "org.springframework.security.web.access.intercept.FilterSecurityInterceptor"
    }

Nota: li ottengo creando un semplice RestController che @Autowires il FilterChainProxy e restituisce il suo contenuto:

    @Autowired
    private FilterChainProxy filterChainProxy;

    @Override
    @RequestMapping("/filterChain")
    public @ResponseBody Map<Integer, Map<Integer, String>> getSecurityFilterChainProxy(){
        return this.getSecurityFilterChainProxy();
    }

    public Map<Integer, Map<Integer, String>> getSecurityFilterChainProxy(){
        Map<Integer, Map<Integer, String>> filterChains= new HashMap<Integer, Map<Integer, String>>();
        int i = 1;
        for(SecurityFilterChain secfc :  this.filterChainProxy.getFilterChains()){
            //filters.put(i++, secfc.getClass().getName());
            Map<Integer, String> filters = new HashMap<Integer, String>();
            int j = 1;
            for(Filter filter : secfc.getFilters()){
                filters.put(j++, filter.getClass().getName());
            }
            filterChains.put(i++, filters);
        }
        return filterChains;
    }

Qui potremmo vedere che solo dichiarando l'elemento <security:http> con una configurazione minima, sono inclusi tutti i filtri predefiniti, ma nessuno di questi è di tipo Authentication (4 ° gruppo nella sezione 13.3 Filter Ordering). Quindi in realtà significa che solo dichiarando l'elemento security:http, SecurityContextPersistenceFilter, ExceptionTranslationFilter e FilterSecurityInterceptor vengono configurati automaticamente.

In effetti, è necessario configurare un meccanismo di elaborazione dell'autenticazione e persino i bean di namespace di sicurezza che elaborano le richieste per questo, generando un errore durante l'avvio, ma può essere ignorato aggiungendo un attributo entry-point-ref in <http:security>

Se aggiungo un <form-login> di base alla configurazione, in questo modo:

<security:http authentication-manager-ref="mainAuthenticationManager">
    <security:intercept-url pattern="/sectest/zone1/**" access="hasRole('ROLE_ADMIN')"/>
    <security:form-login />
</security:http>

Ora, il filterChain sarà così:

{
        "1": "org.springframework.security.web.context.SecurityContextPersistenceFilter",
        "2": "org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter",
        "3": "org.springframework.security.web.header.HeaderWriterFilter",
        "4": "org.springframework.security.web.csrf.CsrfFilter",
        "5": "org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter",
        "6": "org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter",
        "7": "org.springframework.security.web.savedrequest.RequestCacheAwareFilter",
        "8": "org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter",
        "9": "org.springframework.security.web.authentication.AnonymousAuthenticationFilter",
        "10": "org.springframework.security.web.session.SessionManagementFilter",
        "11": "org.springframework.security.web.access.ExceptionTranslationFilter",
        "12": "org.springframework.security.web.access.intercept.FilterSecurityInterceptor"
    }

Ora, questi due filtri org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter e org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter vengono creati e configurato in FilterChainProxy.

Quindi, ora, le domande:

È che per la primavera fornita da login-form, UsernamePasswordAuthenticationFilter viene usato solo per/login, e gli ultimi filtri no?

Sì, viene utilizzato per provare a completare un meccanismo di elaborazione dell'accesso nel caso in cui la richiesta corrisponda all'url UsernamePasswordAuthenticationFilter. Questo URL può essere configurato o anche modificato il suo comportamento per soddisfare ogni richiesta.

Potresti anche avere più di un meccanismo di elaborazione di autenticazione configurato nello stesso FilterchainProxy (come HttpBasic, CAS, ecc.).

L'elemento dello spazio dei nomi login form-configura automaticamente questi filtri?

No, l'elemento login form configura UsernamePasswordAUthenticationFilter e, nel caso in cui non si fornisca un URL di login, configura anche org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter, che termina con un semplice login autogenerato pagina.

Gli altri filtri vengono automaticamente configurati per impostazione predefinita creando semplicemente un elemento <security:http> senza attributo security:"none".

Ogni richiesta (autenticata o meno) raggiunge FilterSecurityInterceptor per URL non di accesso?

Ogni richiesta dovrebbe raggiungerla, in quanto è l'elemento che si prende cura se la richiesta ha i diritti per raggiungere l'url richiesto. Ma alcuni dei filtri elaborati prima potrebbero interrompere l'elaborazione della catena di filtri semplicemente non chiamando FilterChain.doFilter(request, response);. Ad esempio, un filtro CSRF potrebbe interrompere l'elaborazione della catena di filtri se la richiesta non ha il parametro csrf.

Cosa succede se voglio proteggere la mia REST API con token JWT, che viene recuperato dal login? Devo configurare due tag http di configurazione dello spazio dei nomi, i diritti? Altro per/login con UsernamePasswordAuthenticationFilter, e un altro per REST url, con JwtAuthenticationFilter personalizzata.

No, non sei obbligato a fare così. È possibile dichiarare UsernamePasswordAuthenticationFilter e JwtAuthenticationFilter nello stesso elemento http, ma dipende dal comportamento concreto di ciascuno di questi filtri. Entrambi gli approcci sono possibili e quale scegliere dipende in fin dei conti dalle proprie preferenze.

La configurazione di due elementi http crea due springSecurityFitlerChains?

Sì è vero

UsernamePasswordAuthenticationFilter è disattivato per impostazione predefinita, fino a quando non dichiari form-login?

Sì, puoi vederlo nei filtri raccolti in ciascuna delle configurazioni che ho postato

Come sostituire SecurityContextPersistenceFilter con uno, che otterrà l'autenticazione dal token JWT esistente anziché da JSESSIONID?

È possibile evitare SecurityContextPersistenceFilter, semplicemente configurando la strategia di sessione in <http:element>. Basta configurare in questo modo:

<security:http create-session="stateless" >

Oppure, in questo caso potresti sovrascriverlo con un altro filtro, in questo modo all'interno dell'elemento <security:http>:

<security:http ...>  
   <security:custom-filter ref="myCustomFilter" position="SECURITY_CONTEXT_FILTER"/>    
</security:http>
<beans:bean id="myCustomFilter" class="com.xyz.myFilter" />

MODIFICARE:

Una domanda su "Si potrebbe anche avere più di un meccanismo di elaborazione di autenticazione configurato nella stessa FilterchainProxy". Quest'ultimo sovrascriverà l'autenticazione eseguita dal primo, se dichiarerà più filtri di autenticazione (implementazione a molla)? In che modo si tratta di avere più provider di autenticazione?

Questo dipende, infine, dall'implementazione di ciascun filtro stesso, ma è vero che questi ultimi filtri di autenticazione sono almeno in grado di sovrascrivere qualsiasi autenticazione precedente effettuata dai precedenti filtri.

Ma questo non avverrà necessariamente. Ho alcuni casi di produzione in servizi sicuri REST in cui utilizzo un tipo di token di autorizzazione che può essere fornito sia come intestazione Http o all'interno del corpo della richiesta. Così configuro due filtri che recuperano quel token, in un caso dall'intestazione Http e l'altro dal corpo della richiesta della propria richiesta di riposo. È vero che se una richiesta http fornisce quel token di autenticazione sia come intestazione Http che all'interno del corpo della richiesta, entrambi i filtri proveranno ad eseguire il meccanismo di autenticazione delegandolo al gestore, ma potrebbe essere facilmente evitato semplicemente controllando se la richiesta è già autenticato solo all'inizio del metodo doFilter() di ciascun filtro.

Avere più di un filtro di autenticazione è correlato ad avere più di un provider di autenticazione, ma non forzarlo. Nel caso che ho esposto prima, ho due filtri di autenticazione ma ho solo un provider di autenticazione, in quanto entrambi i filtri creano lo stesso tipo di oggetto di autenticazione, quindi in entrambi i casi il gestore di autenticazione lo assegna allo stesso provider.

E di fronte a questo, anch'io ho uno scenario in cui pubblico solo un UsernamePasswordAuthenticationFilter ma le credenziali dell'utente possono essere contenute in DB o LDAP, quindi ho due UsernamePasswordAuthenticationToken che supportano i provider e AuthenticationManager delega qualsiasi tentativo di autenticazione dal filtro ai provider in modo riservato per convalidare le credenziali.

Quindi, penso sia chiaro che né la quantità di filtri di autenticazione determina la quantità di provider di autenticazione né la quantità di provider che determina la quantità di filtri.

Inoltre, la documentazione afferma che SecurityContextPersistenceFilter è responsabile della pulizia di SecurityContext, che è importante per il pool di thread. Se lo ometto o fornisco un'implementazione personalizzata, devo implementare la pulizia manualmente, giusto? Ci sono trucchi più simili durante la personalizzazione della catena?

Non ho guardato attentamente questo filtro prima, ma dopo l'ultima domanda che ho controllato è l'implementazione e, come sempre in primavera, quasi tutto potrebbe essere configurato, esteso o sovrascritto.

I SecurityContextPersistenceFilter delegati in un SecurityContextRepository implementano la ricerca di SecurityContext. Per impostazione predefinita, viene utilizzato un HttpSessionSecurityContextRepository , ma questo potrebbe essere modificato utilizzando uno dei costruttori del filtro. Quindi potrebbe essere meglio scrivere un SecurityContextRepository che soddisfi le tue esigenze e configurarlo in SecurityContextPersistenceFilter, fidandoti del suo comportamento provato piuttosto che iniziare a fare tutto da zero.

140
jlumietu

UsernamePasswordAuthenticationFilter è usato solo per /login, e gli ultimi filtri no?

No, UsernamePasswordAuthenticationFilter estende AbstractAuthenticationProcessingFilter, e questo contiene un RequestMatcher, che significa che è possibile definire il proprio URL di elaborazione, questo filtro gestisce solo RequestMatcher corrisponde all'url di richiesta, l'url di elaborazione predefinito è /login.

I filtri successivi possono ancora gestire la richiesta, se UsernamePasswordAuthenticationFilter esegue chain.doFilter(request, response);.

Maggiori dettagli su core fitlers

L'elemento dello spazio dei nomi login form-configura automaticamente questi filtri?

UsernamePasswordAuthenticationFilter è creato da <form-login>, questi sono Alias ​​e ordinamenti di filtro standard

Ogni richiesta (autenticata o meno) raggiunge FilterSecurityInterceptor per URL non di accesso?

Dipende se i precompilatori hanno successo, ma FilterSecurityInterceptor è l'ultimo fitler normalmente.

La configurazione di due elementi http crea due springSecurityFitlerChains?

Sì, ogni fitlerChain ha un RequestMatcher, se RequestMatcher corrisponde alla richiesta, la richiesta verrà gestita dai fitler nella catena di adattamento.

Il RequestMatcher predefinito corrisponde a tutte le richieste se non si configura il pattern, oppure è possibile configurare l'URL specifico (<http pattern="/rest/**").

Se vuoi saperne di più sui fitlers, penso che puoi controllare il codice sorgente in primavera. doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)

4
chaoluo