Die Federsicherheitsfilterkette ist ein sehr komplexer und flexibler Motor.
Schlüsselfilter in der Kette sind (in der Reihenfolge)
- SecurityContextPersistenceFilter (stellt die Authentifizierung von JSESSIONID wieder her)
- UsernamePasswordAuthenticationFilter (führt die Authentifizierung durch)
- ExceptionTranslationFilter (Sicherheitsausnahmen von FilterSecurityInterceptor abfangen)
- FilterSecurityInterceptor (kann Authentifizierungs- und Autorisierungsausnahmen auslösen)
In der aktuellen Dokumentation zu Stable Release 4.2.1 , Abschnitt 13.3 Filterreihenfolge, sehen Sie die Filterorganisation der gesamten Filterkette:
13.3 Filterbestellung
Die Reihenfolge, in der Filter in der Kette definiert werden, ist sehr wichtig. Unabhängig davon, welche Filter Sie tatsächlich verwenden, sollte die Reihenfolge wie folgt lauten:
ChannelProcessingFilter , da möglicherweise eine Umleitung zu einem anderen Protokoll erforderlich ist
SecurityContextPersistenceFilter , sodass zu Beginn einer Webanforderung ein SecurityContext im SecurityContextHolder eingerichtet werden kann und alle Änderungen am SecurityContext nach Beendigung der Webanforderung in die HttpSession kopiert werden können (zur Verwendung mit der nächsten Webanforderung bereit).
ConcurrentSessionFilter , da es die SecurityContextHolder-Funktionalität verwendet und die SessionRegistry aktualisieren muss, um laufende Anforderungen des Principals widerzuspiegeln
Authentifizierungsverarbeitungsmechanismen -
UsernamePasswordAuthenticationFilter , CasAuthenticationFilter ,
BasicAuthenticationFilter usw. - damit der SecurityContextHolder so geändert werden kann, dass er ein gültiges Authentifizierungsanforderungstoken enthält
Der SecurityContextHolderAwareRequestFilter , wenn Sie ihn verwenden, um einen Spring Security-fähigen HttpServletRequestWrapper in Ihrem Servlet-Container zu installieren
Der JaasApiIntegrationFilter , wenn sich ein JaasAuthenticationToken im SecurityContextHolder befindet, verarbeitet die FilterChain als Betreff im JaasAuthenticationToken
RememberMeAuthenticationFilter . Wenn also kein früherer Authentifizierungsverarbeitungsmechanismus den SecurityContextHolder aktualisiert hat und die Anforderung ein Cookie enthält, mit dem Remember-Me-Dienste ausgeführt werden können, wird dort ein geeignetes gespeichertes Authentifizierungsobjekt abgelegt
AnonymousAuthenticationFilter , sodass ein anonymer Authentifizierungsobjekt dort abgelegt wird, wenn kein früherer Authentifizierungsverarbeitungsmechanismus den SecurityContextHolder aktualisiert hat
ExceptionTranslationFilter , um alle Spring Security-Ausnahmen abzufangen, sodass entweder eine HTTP-Fehlerantwort zurückgegeben oder ein geeigneter AuthenticationEntryPoint gestartet werden kann
FilterSecurityInterceptor , um Web-URIs zu schützen und Ausnahmen auszulösen , wenn der Zugriff verweigert wird
Jetzt werde ich versuchen, Ihre Fragen einzeln zu beantworten:
Ich bin verwirrt, wie diese Filter verwendet werden. Wird für den im Frühjahr bereitgestellten Formular-Login UsernamePasswordAuthenticationFilter nur für / login verwendet und letztere Filter nicht? Konfiguriert das Formularanmelde-Namespace-Element diese Filter automatisch? Erreicht jede Anforderung (authentifiziert oder nicht) FilterSecurityInterceptor für eine nicht angemeldete URL?
Sobald Sie einen <security-http>
Abschnitt konfiguriert haben , müssen Sie für jeden Abschnitt mindestens einen Authentifizierungsmechanismus bereitstellen. Dies muss einer der Filter sein, die mit Gruppe 4 im Abschnitt 13.3 Filterreihenfolge aus der Spring Security-Dokumentation übereinstimmen, auf die ich gerade verwiesen habe.
Dies ist das minimal gültige Sicherheitselement: http, das konfiguriert werden kann:
<security:http authentication-manager-ref="mainAuthenticationManager"
entry-point-ref="serviceAccessDeniedHandler">
<security:intercept-url pattern="/sectest/zone1/**" access="hasRole('ROLE_ADMIN')"/>
</security:http>
Dabei werden diese Filter im Filterketten-Proxy konfiguriert:
{
"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"
}
Hinweis: Ich erhalte sie, indem ich einen einfachen RestController erstelle, der den FilterChainProxy @Autowires verwendet und dessen Inhalt zurückgibt:
@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;
}
Hier konnten wir sehen, dass nur durch Deklarieren des <security:http>
Elements mit einer Mindestkonfiguration alle Standardfilter enthalten sind, aber keiner von ihnen vom Authentifizierungstyp ist (4. Gruppe im Abschnitt 13.3 Filterreihenfolge). security:http
Dies bedeutet also, dass nur durch Deklarieren des Elements der SecurityContextPersistenceFilter, der ExceptionTranslationFilter und der FilterSecurityInterceptor automatisch konfiguriert werden.
Tatsächlich sollte ein Authentifizierungsverarbeitungsmechanismus konfiguriert werden, und sogar Sicherheits-Namespace-Beans, die Ansprüche dafür verarbeiten, werfen beim Start einen Fehler aus, der jedoch umgangen werden kann, indem ein Einstiegspunkt-Referenzattribut hinzugefügt wird <http:security>
Wenn ich <form-login>
der Konfiguration ein Basic hinzufüge , gehen Sie folgendermaßen vor:
<security:http authentication-manager-ref="mainAuthenticationManager">
<security:intercept-url pattern="/sectest/zone1/**" access="hasRole('ROLE_ADMIN')"/>
<security:form-login />
</security:http>
Jetzt sieht die filterChain folgendermaßen aus:
{
"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"
}
Diese beiden Filter org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter und org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter werden nun in FilterChainProxy erstellt und konfiguriert.
Nun also die Fragen:
Wird für den im Frühjahr bereitgestellten Formular-Login UsernamePasswordAuthenticationFilter nur für / login verwendet und letztere Filter nicht?
Ja, es wird verwendet, um zu versuchen, einen Anmeldeverarbeitungsmechanismus abzuschließen, falls die Anforderung mit der URL UsernamePasswordAuthenticationFilter übereinstimmt. Diese URL kann so konfiguriert oder sogar geändert werden, dass sie jeder Anforderung entspricht.
Möglicherweise können auch mehrere Authentifizierungsverarbeitungsmechanismen in derselben FilterchainProxy konfiguriert sein (z. B. HttpBasic, CAS usw.).
Konfiguriert das Formularanmelde-Namespace-Element diese Filter automatisch?
Nein, das Formularanmeldeelement konfiguriert den UsernamePasswordAUthenticationFilter. Falls Sie keine Anmeldeseiten-URL angeben, konfiguriert es auch den org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter, der mit einer einfachen automatisch generierten Anmeldung endet Seite.
Die anderen Filter werden standardmäßig automatisch konfiguriert, indem nur ein <security:http>
Element ohne security:"none"
Attribut erstellt wird.
Erreicht jede Anforderung (authentifiziert oder nicht) FilterSecurityInterceptor für eine nicht angemeldete URL?
Jede Anfrage sollte sie erreichen, da es das Element ist, das dafür sorgt, ob die Anfrage die Rechte hat, die angeforderte URL zu erreichen. Einige der zuvor verarbeiteten Filter stoppen jedoch möglicherweise die Filterkettenverarbeitung und rufen einfach nicht auf FilterChain.doFilter(request, response);
. Beispielsweise kann ein CSRF-Filter die Filterkettenverarbeitung stoppen, wenn die Anforderung nicht den Parameter csrf enthält.
Was ist, wenn ich meine REST-API mit einem JWT-Token sichern möchte, das vom Login abgerufen wird? Ich muss zwei Namespace-Konfigurations-http-Tags konfigurieren, Rechte? Andere für / login mit UsernamePasswordAuthenticationFilter
und eine andere für REST-URLs mit benutzerdefinierten JwtAuthenticationFilter
.
Nein, Sie sind nicht gezwungen, dies zu tun. Sie können beide UsernamePasswordAuthenticationFilter
und das JwtAuthenticationFilter
im selben http-Element deklarieren , dies hängt jedoch vom konkreten Verhalten jedes dieser Filter ab. Beide Ansätze sind möglich, und welcher letztendlich gewählt werden kann, hängt von den eigenen Vorlieben ab.
Erstellt das Konfigurieren von zwei http-Elementen zwei springSecurityFitlerChains?
Ja das stimmt
Ist UsernamePasswordAuthenticationFilter standardmäßig deaktiviert, bis ich die Formularanmeldung deklariere?
Ja, Sie konnten es in den Filtern sehen, die in jeder der von mir geposteten Konfigurationen ausgelöst wurden
Wie ersetze ich SecurityContextPersistenceFilter durch einen, der die Authentifizierung von einem vorhandenen JWT-Token anstelle von JSESSIONID erhält?
Sie können SecurityContextPersistenceFilter vermeiden, indem Sie nur die Sitzungsstrategie in konfigurieren <http:element>
. Einfach so konfigurieren:
<security:http create-session="stateless" >
In diesem Fall können Sie es auch mit einem anderen Filter überschreiben, und zwar auf diese Weise innerhalb des <security:http>
Elements:
<security:http ...>
<security:custom-filter ref="myCustomFilter" position="SECURITY_CONTEXT_FILTER"/>
</security:http>
<beans:bean id="myCustomFilter" class="com.xyz.myFilter" />
BEARBEITEN:
Eine Frage zu "Sie könnten auch mehr als einen Authentifizierungsverarbeitungsmechanismus in derselben FilterchainProxy konfigurieren lassen". Überschreibt letzterer die von der ersten durchgeführte Authentifizierung, wenn mehrere Authentifizierungsfilter (Spring-Implementierung) deklariert werden? Wie hängt dies mit mehreren Authentifizierungsanbietern zusammen?
Dies hängt letztendlich von der Implementierung jedes Filters selbst ab, aber es ist wahr, dass die letzteren Authentifizierungsfilter zumindest jede vorherige Authentifizierung überschreiben können, die eventuell von vorhergehenden Filtern vorgenommen wurde.
Dies wird aber nicht unbedingt passieren. Ich habe einige Produktionsfälle in gesicherten REST-Diensten, in denen ich eine Art Autorisierungstoken verwende, das sowohl als HTTP-Header als auch innerhalb des Anforderungshauptteils bereitgestellt werden kann. Daher konfiguriere ich zwei Filter, die dieses Token wiederherstellen, in einem Fall aus dem HTTP-Header und dem anderen aus dem Anforderungshauptteil der eigenen Restanforderung. Es ist wahr, dass beide Filter versuchen, den Authentifizierungsmechanismus auszuführen, der ihn an den Manager delegiert, wenn eine http-Anforderung dieses Authentifizierungstoken sowohl als HTTP-Header als auch innerhalb des Anforderungshauptteils bereitstellt. Es könnte jedoch leicht vermieden werden, einfach zu überprüfen, ob die Anforderung vorliegt bereits zu Beginn der doFilter()
Methode jedes Filters authentifiziert .
Wenn Sie mehr als einen Authentifizierungsfilter haben, müssen Sie mehr als einen Authentifizierungsanbieter haben, aber erzwingen Sie dies nicht. In dem Fall, den ich zuvor verfügbar gemacht habe, habe ich zwei Authentifizierungsfilter, aber nur einen Authentifizierungsanbieter, da beide Filter denselben Typ von Authentifizierungsobjekt erstellen, sodass der Authentifizierungsmanager ihn in beiden Fällen an denselben Anbieter delegiert.
Im Gegensatz dazu habe auch ich ein Szenario, in dem ich nur einen UsernamePasswordAuthenticationFilter veröffentliche, aber die Benutzeranmeldeinformationen können beide in DB oder LDAP enthalten sein, sodass ich zwei unterstützende UsernamePasswordAuthenticationToken-Anbieter habe und der AuthenticationManager jeden Authentifizierungsversuch vom Filter an die Anbieter delegiert nacheinander, um die Anmeldeinformationen zu überprüfen.
Ich denke, es ist klar, dass weder die Anzahl der Authentifizierungsfilter die Anzahl der Authentifizierungsanbieter noch die Anzahl der Anbieter die Anzahl der Filter bestimmt.
In der Dokumentation heißt es außerdem, dass SecurityContextPersistenceFilter für die Bereinigung des SecurityContext verantwortlich ist, was aufgrund des Thread-Poolings wichtig ist. Wenn ich es weglasse oder eine benutzerdefinierte Implementierung bereitstelle, muss ich die Reinigung manuell implementieren, oder? Gibt es ähnliche Fallstricke beim Anpassen der Kette?
Ich habe diesen Filter vorher nicht genau untersucht, aber nach Ihrer letzten Frage habe ich die Implementierung überprüft, und wie im Frühjahr üblich konnte fast alles konfiguriert, erweitert oder überschrieben werden.
Der SecurityContextPersistenceFilter delegiert in einer SecurityContextRepository- Implementierung die Suche nach dem SecurityContext. Standardmäßig wird ein HttpSessionSecurityContextRepository verwendet. Dies kann jedoch mithilfe eines der Konstruktoren des Filters geändert werden. Daher ist es möglicherweise besser, ein SecurityContextRepository zu schreiben, das Ihren Anforderungen entspricht, und es einfach im SecurityContextPersistenceFilter zu konfigurieren, wobei Sie auf das bewährte Verhalten vertrauen, anstatt alles von Grund auf neu zu erstellen.