La catena del filtro di sicurezza Spring è un motore molto complesso e flessibile.
I filtri chiave nella catena sono (nell'ordine)
- SecurityContextPersistenceFilter (ripristina l'autenticazione da JSESSIONID)
- UsernamePasswordAuthenticationFilter (esegue l'autenticazione)
- ExceptionTranslationFilter (rileva le eccezioni di sicurezza da FilterSecurityInterceptor)
- FilterSecurityInterceptor (può generare eccezioni di autenticazione e autorizzazione)
Guardando l' attuale documentazione della versione stabile 4.2.1 , sezione 13.3 Ordinamento dei filtri è possibile vedere l'intera organizzazione dei filtri della catena di filtri:
13.3 Ordinamento del filtro
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:
ChannelProcessingFilter , perché potrebbe essere necessario reindirizzare a un protocollo diverso
SecurityContextPersistenceFilter , quindi un SecurityContext può essere impostato in SecurityContextHolder all'inizio di una richiesta Web e qualsiasi modifica a SecurityContext può essere copiata in HttpSession al termine della richiesta Web (pronta per l'uso con la successiva richiesta Web)
ConcurrentSessionFilter , perché utilizza la funzionalità SecurityContextHolder e deve aggiornare SessionRegistry per riflettere le richieste in corso dall'entità
Meccanismi di elaborazione dell'autenticazione -
UsernamePasswordAuthenticationFilter , CasAuthenticationFilter ,
BasicAuthenticationFilter ecc. - In modo che SecurityContextHolder possa essere modificato per contenere un token di richiesta di autenticazione valido
Il SecurityContextHolderAwareRequestFilter , se lo si utilizza per installare un consapevole Sicurezza Primavera HttpServletRequestWrapper nel vostro servlet container
Il JaasApiIntegrationFilter , se un JaasAuthenticationToken è nel SecurityContextHolder questo elaborerà il FilterChain come il soggetto nel JaasAuthenticationToken
RememberMeAuthenticationFilter , in modo che se nessun meccanismo di elaborazione dell'autenticazione precedente aggiorna SecurityContextHolder e la richiesta presenti un cookie che consenta l'esecuzione dei servizi Remember-Me, verrà inserito un oggetto di autenticazione ricordato adatto
AnonymousAuthenticationFilter , in modo che se nessun meccanismo di elaborazione dell'autenticazione precedente ha aggiornato SecurityContextHolder, verrà inserito un oggetto di autenticazione anonimo
ExceptionTranslationFilter , per rilevare eventuali eccezioni di Spring Security in modo che sia possibile restituire una risposta di errore HTTP o avviare un AuthenticationEntryPoint appropriato
FilterSecurityInterceptor , per proteggere gli URI Web e sollevare eccezioni quando viene negato l'accesso
Ora proverò ad andare avanti con le tue domande una per una:
Sono confuso su come vengono utilizzati questi filtri. È per la primavera fornito form-login, UsernamePasswordAuthenticationFilter viene utilizzato solo per / login e questi ultimi filtri non lo sono? L'elemento dello spazio dei nomi form-login configura automaticamente questi filtri? Ogni richiesta (autenticata o meno) raggiunge FilterSecurityInterceptor per l'URL senza accesso?
Dopo aver configurato una <security-http>
sezione, per ognuna devi fornire almeno un meccanismo di autenticazione. Questo deve essere uno dei filtri che corrispondono al gruppo 4 nella sezione 13.3 Ordine dei filtri della documentazione di Spring Security a cui ho appena fatto riferimento.
Questa è la minima sicurezza 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>
Semplicemente, 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' <security:http>
elemento con una configurazione minima, sono inclusi tutti i filtri predefiniti, ma nessuno di questi è di tipo Autenticazione (4 ° gruppo nella sezione 13.3 Filtro Ordinamento). Quindi in realtà significa che semplicemente dichiarando l' security:http
elemento, SecurityContextPersistenceFilter, ExceptionTranslationFilter e FilterSecurityInterceptor sono configurati automaticamente.
In effetti, dovrebbe essere configurato un meccanismo di elaborazione dell'autenticazione e anche i bean dello spazio dei nomi di sicurezza elaborano le richieste per questo, generando un errore durante l'avvio, ma può essere bypassato aggiungendo un attributo entry-point-ref in <http:security>
Se aggiungo un basic <form-login>
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, 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 sono creati e configurati in FilterChainProxy.
Quindi, ora, le domande:
È per la primavera fornito form-login, UsernamePasswordAuthenticationFilter viene utilizzato solo per / login e questi ultimi filtri non lo sono?
Sì, viene utilizzato per provare a completare un meccanismo di elaborazione dell'accesso nel caso in cui la richiesta corrisponda all'URL di UsernamePasswordAuthenticationFilter. Questo url può essere configurato o addirittura cambiato il suo comportamento per soddisfare ogni richiesta.
Potresti anche avere più di un meccanismo di elaborazione dell'autenticazione configurato nello stesso FilterchainProxy (come HttpBasic, CAS, ecc.).
L'elemento dello spazio dei nomi form-login configura automaticamente questi filtri?
No, l'elemento form-login configura il UsernamePasswordAUthenticationFilter e, nel caso in cui non si fornisca un url della pagina di accesso, configura anche org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter, che termina in un semplice login generato automaticamente pagina.
Gli altri filtri sono configurati automaticamente per impostazione predefinita semplicemente creando un <security:http>
elemento senza security:"none"
attributo.
Ogni richiesta (autenticata o meno) raggiunge FilterSecurityInterceptor per l'URL senza accesso?
Ogni richiesta dovrebbe raggiungerla, in quanto è l'elemento che si occupa di se la richiesta ha i diritti per raggiungere l'URL richiesto. Ma alcuni dei filtri elaborati in precedenza 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 desidero proteggere la mia API REST con token JWT, che viene recuperato dall'accesso? Devo configurare due tag http di configurazione dello spazio dei nomi, diritti? Un altro per / accedi con UsernamePasswordAuthenticationFilter
, e un altro per l'URL REST, con personalizzato JwtAuthenticationFilter
.
No, non sei obbligato a fare così. È possibile dichiarare entrambi 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 alla fine dipende dalle proprie preferenze.
La configurazione di due elementi http crea due springSecurityFitlerChains?
Sì è vero
UsernamePasswordAuthenticationFilter è disattivato per impostazione predefinita, fino a quando non dichiaro form-login?
Sì, potresti vederlo nei filtri sollevati in ognuna delle configurazioni che ho pubblicato
Come posso 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 è possibile sovrascriverlo con un altro filtro, in questo modo all'interno <security:http>
dell'elemento:
<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 "Potresti avere anche più di un meccanismo di elaborazione dell'autenticazione configurato nello stesso FilterchainProxy". Quest'ultimo sovrascriverà l'autenticazione eseguita dalla prima, se si dichiarano più filtri di autenticazione (implementazione Spring)? In che modo ciò comporta avere più provider di autenticazione?
Questo dipende infine dall'implementazione di ciascun filtro stesso, ma è vero il fatto che questi ultimi filtri di autenticazione sono almeno in grado di sovrascrivere qualsiasi autenticazione precedente eventualmente effettuata dai filtri precedenti.
Ma questo non accadrà necessariamente. Ho alcuni casi di produzione in servizi REST protetti in cui utilizzo un tipo di token di autorizzazione che può essere fornito sia come intestazione Http che all'interno del corpo della richiesta. Quindi 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 il fatto che se una richiesta http fornisce quel token di autenticazione sia come intestazione Http sia all'interno del corpo della richiesta, entrambi i filtri proveranno ad eseguire il meccanismo di autenticazione delegandolo al gestore, ma si potrebbe facilmente evitare semplicemente controllando se la richiesta è già autenticato solo all'inizio del doFilter()
metodo 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 in precedenza, ho due filtri di autenticazione ma ho solo un provider di autenticazione, poiché entrambi i filtri creano lo stesso tipo di oggetto di autenticazione, quindi in entrambi i casi il gestore dell'autenticazione lo delega allo stesso provider.
E al contrario di questo, anch'io ho uno scenario in cui pubblico solo un UsernamePasswordAuthenticationFilter ma le credenziali dell'utente possono essere entrambe contenute in DB o LDAP, quindi ho due UsernamePasswordAuthenticationToken che supportano i provider, e AuthenticationManager delega qualsiasi tentativo di autenticazione dal filtro ai provider secuentially 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 determinano la quantità di filtri.
Inoltre, la documentazione afferma che SecurityContextPersistenceFilter è responsabile della pulizia di SecurityContext, che è importante a causa del pool di thread. Se lo ometto o fornisco un'implementazione personalizzata, devo implementare la pulizia manualmente, giusto? Ci sono più gotcha simili quando personalizzi la catena?
Non avevo esaminato attentamente questo filtro prima, ma dopo la tua ultima domanda ho controllato la sua implementazione e, come al solito in primavera, quasi tutto poteva essere configurato, esteso o sovrascritto.
I SecurityContextPersistenceFilter delegati in un SecurityContextRepository implementazione della ricerca per la 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 si adatta alle tue esigenze e configurarlo nel SecurityContextPersistenceFilter, confidando nel suo comportamento provato piuttosto che iniziare a fare tutto da zero.