it-swarm.com.de

So funktioniert Spring Security Filter Chain

Mir ist klar, dass Spring Security auf einer Kette von Filtern aufbaut, die die Anforderung abfangen, die Authentifizierung erkennen (nicht), zur Authentifizierung umleiten oder die Anforderung an den Autorisierungsdienst weiterleiten und die Anforderung schließlich entweder das Servlet treffen oder eine Sicherheitsausnahme auslösen (nicht authentifiziert oder nicht autorisiert). DelegatingFitlerProxy klebt diese Filter zusammen. Um ihre Aufgaben auszuführen, greifen diese Filter auf Dienste wie UserDetailsService und AuthenticationManager zu.

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)

Ich bin verwirrt, wie diese Filter verwendet werden. Ist es so, dass für die im Frühjahr bereitgestellte Formularanmeldung UsernamePasswordAuthenticationFilter nur für /login verwendet wird und letztere Filter nicht? Konfiguriert das Namespace-Element form-login diese Filter automatisch? Erreicht jede (authentifizierte oder nicht authentifizierte) Anforderung FilterSecurityInterceptor für eine nicht angemeldete URL?

Was ist, wenn ich meine REST API mit JWT-Token sichern möchte, das vom Login abgerufen wird? Ich muss Konfigurieren Sie zwei Namespace-Konfigurations-Tags http, Rechte? Anderes für /login mit UsernamePasswordAuthenticationFilter und eines für REST URLs mit benutzerdefiniertem JwtAuthenticationFilter.

Werden durch die Konfiguration von zwei http Elementen zwei springSecurityFitlerChains erstellt? Ist UsernamePasswordAuthenticationFilter standardmäßig deaktiviert, bis ich form-login Deklariere? Wie ersetze ich SecurityContextPersistenceFilter durch einen, der Authentication aus dem vorhandenen JWT-token Anstelle von JSESSIONID erhält?

98
Tuomas Toivonen

Die Spring Sicherheitsfilterkette 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 aktuelle stabile Version 4.2.1-Dokumentation , Abschnitt 13.3 Filterreihenfolge sehen Sie die Filterorganisation der gesamten Filterkette:

13.3 Filterbestellung

Die Reihenfolge, in der die Filter in der Kette definiert sind, ist sehr wichtig. Unabhängig davon, welche Filter Sie tatsächlich verwenden, sollte die Reihenfolge wie folgt lauten:

  1. ChannelProcessingFilter, da möglicherweise auf ein anderes Protokoll umgeleitet werden muss

  2. SecurityContextPersistenceFilter, damit zu Beginn einer Webanfrage und bei Änderungen ein SecurityContext im SecurityContextHolder eingerichtet werden kann zum SecurityContext kann nach Beendigung der Webanfrage in die HttpSession kopiert werden (einsatzbereit für die nächste Webanfrage)

  3. ConcurrentSessionFilter, da die SecurityContextHolder-Funktionalität verwendet wird und das SessionRegistry aktualisiert werden muss, um laufende Anforderungen des Principals widerzuspiegeln

  4. Authentifizierungsverarbeitungsmechanismen - UsernamePasswordAuthenticationFilter, CasAuthenticationFilter, BasicAuthenticationFilter etc - damit der SecurityContextHolder geändert werden kann Enthält ein gültiges Authentifizierungsanforderungstoken

  5. Das SecurityContextHolderAwareRequestFilter, wenn Sie damit einen Spring Security-fähigen HttpServletRequestWrapper in Ihrem Servlet-Container installieren

  6. Das JaasApiIntegrationFilter, wenn sich ein JaasAuthenticationToken im SecurityContextHolder befindet, wird das verarbeitet FilterChain als Betreff im JaasAuthenticationToken

  7. RememberMeAuthenticationFilter, sodass, wenn kein früherer Authentifizierungsverarbeitungsmechanismus den SecurityContextHolder aktualisiert hat, die Anforderung ein Cookie anzeigt, das das Speichern ermöglicht -müssen Dienste stattfinden, wird dort ein passendes gespeichertes Authentifizierungsobjekt abgelegt

  8. AnonymousAuthenticationFilter, sodass, wenn kein früherer Authentifizierungsverarbeitungsmechanismus den SecurityContextHolder aktualisiert, ein anonymes Authentifizierungsobjekt dort abgelegt wird

  9. ExceptionTranslationFilter, um alle Spring Security-Ausnahmen abzufangen, sodass entweder eine HTTP-Fehlerantwort zurückgegeben werden kann oder ein entsprechender AuthenticationEntryPoint ins Leben gerufen werden

  10. FilterSecurityInterceptor, um Web-URIs zu schützen und Ausnahmen auszulösen, wenn der Zugriff verweigert wird

Nun werde ich versuchen, Ihre Fragen nacheinander zu beantworten:

Ich bin verwirrt, wie diese Filter verwendet werden. Ist es so, dass für die im Frühjahr bereitgestellte Formularanmeldung UsernamePasswordAuthenticationFilter nur für/login verwendet wird und letztere Filter nicht? Konfiguriert das Namespace-Element für die Formularanmeldung diese Filter automatisch? Erreicht jede (authentifizierte oder nicht authentifizierte) Anfrage den FilterSecurityInterceptor für eine nicht angemeldete URL?

Wenn Sie einen Abschnitt <security-http> Konfiguriert haben, müssen Sie für jeden mindestens einen Authentifizierungsmechanismus angeben. Dies muss einer der Filter sein, die mit Gruppe 4 im Abschnitt 13.3 Filterreihenfolge aus der soeben erwähnten Spring Security-Dokumentation übereinstimmen.

Dies ist das minimal gültige security: http-Element, 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>

Wenn Sie dies tun, 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 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 Elements <security:http> Mit einer Mindestkonfiguration alle Standardfilter enthalten sind, aber keiner von ihnen vom Authentifizierungstyp ist (4. Gruppe in Abschnitt 13.3, Filterreihenfolge). Das bedeutet, dass durch die Deklaration des Elements security:http Der SecurityContextPersistenceFilter, der ExceptionTranslationFilter und der FilterSecurityInterceptor automatisch konfiguriert werden.

In der Tat sollte ein Authentifizierungs-Verarbeitungsmechanismus konfiguriert werden, und sogar Sicherheits-Namespace-Beans, die Ansprüche dafür verarbeiten und beim Start einen Fehler auslösen, der jedoch umgangen werden kann, indem ein Einstiegspunkt-Ref-Attribut in <http:security> Hinzugefügt wird.

Wenn ich der Konfiguration ein grundlegendes <form-login> 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>

Nun sieht die filterChain so 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"
    }

Nun werden diese beiden Filter org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter und org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter im FilterChainProxy erstellt und konfiguriert.

Nun also zu den Fragen:

Ist es so, dass für die im Frühjahr bereitgestellte Formularanmeldung UsernamePasswordAuthenticationFilter nur für/login verwendet wird und letztere Filter nicht?

Ja, es wird verwendet, um zu versuchen, einen Anmeldeverarbeitungsmechanismus abzuschließen, falls die Anforderung mit der UsernamePasswordAuthenticationFilter-URL übereinstimmt. Diese URL kann so konfiguriert oder sogar geändert werden, dass sie mit jeder Anfrage übereinstimmt.

Sie können auch mehrere Authentifizierungsverarbeitungsmechanismen in demselben FilterchainProxy konfigurieren (z. B. HttpBasic, CAS usw.).

Konfiguriert das Namespace-Element für die Formularanmeldung diese Filter automatisch?

Nein, das form-login-Element konfiguriert den UsernamePasswordAUthenticationFilter und, falls Sie keine Anmeldeseiten-URL angeben, auch die org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter, die mit einer einfachen automatisch generierten Anmeldung endet Seite.

Die anderen Filter werden standardmäßig automatisch konfiguriert, indem lediglich ein <security:http> - Element ohne security:"none" - Attribut erstellt wird.

Erreicht jede (authentifizierte oder nicht authentifizierte) Anfrage den FilterSecurityInterceptor für eine nicht angemeldete URL?

Jede Anfrage sollte es erreichen, da es das Element ist, das sich darum kümmert, ob die Anfrage das Recht hat, die angeforderte URL zu erreichen. Einige der zuvor verarbeiteten Filter beenden jedoch möglicherweise die Filterkettenverarbeitung, ohne FilterChain.doFilter(request, response); aufzurufen. 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 JWT-Token sichern möchte, das von der Anmeldung abgerufen wird? Ich muss zwei http-Tags für die Namespace-Konfiguration konfigurieren, Rechte? Anderes für/login mit UsernamePasswordAuthenticationFilter und eine andere für REST url's, mit benutzerdefiniertem JwtAuthenticationFilter.

Nein, Sie sind nicht gezwungen, dies zu tun. Sie können sowohl UsernamePasswordAuthenticationFilter als auch JwtAuthenticationFilter im selben http-Element deklarieren, dies hängt jedoch vom konkreten Verhalten der einzelnen Filter ab. Beide Ansätze sind möglich, und welche man letztendlich wählt, hängt von den eigenen Vorlieben ab.

Werden durch die Konfiguration von zwei http-Elementen zwei springSecurityFitlerChains erstellt?

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

Wie ersetze ich SecurityContextPersistenceFilter durch einen, der die Authentifizierung von einem vorhandenen JWT-Token anstelle von JSESSIONID erhält?

Sie könnten SecurityContextPersistenceFilter umgehen, indem Sie einfach Sitzungsstrategie in <http:element> Konfigurieren. Einfach so konfigurieren:

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

In diesem Fall können Sie es mit einem anderen Filter überschreiben, und zwar so innerhalb des Elements <security:http>:

<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 "Es können auch mehrere Authentifizierungsverarbeitungsmechanismen in demselben FilterchainProxy konfiguriert sein". Überschreibt letzterer die von erstem durchgeführte Authentifizierung, wenn mehrere Authentifizierungsfilter (Spring-Implementierung) deklariert werden? In welcher Beziehung stehen mehrere Authentifizierungsanbieter?

Dies hängt letztendlich von der Implementierung jedes Filters selbst ab, aber es ist wahr, dass die letzteren Authentifizierungsfilter in der Lage sind, jegliche vorherige Authentifizierung zu überschreiben, die eventuell von vorhergehenden Filtern vorgenommen wurde.

Das wird aber nicht unbedingt passieren. Ich habe einige Produktionsfälle in gesicherten REST Diensten, bei denen ich eine Art Autorisierungstoken verwende, das sowohl als HTTP-Header als auch im Anfragetext bereitgestellt werden kann. Deshalb konfiguriere ich zwei Filter, die dieses Token wiederherstellen In einem Fall aus dem HTTP-Header und dem anderen aus dem Anforderungshauptteil der eigenen Restanforderung. Wenn eine http-Anforderung dieses Authentifizierungstoken sowohl als HTTP-Header als auch innerhalb des Anforderungshauptteils bereitstellt, versuchen beide Filter auszuführen der Authentifizierungsmechanismus delegiert es an den Manager, aber es könnte leicht vermieden werden, einfach zu prüfen, ob die Anforderung bereits am Anfang der doFilter() -Methode jedes Filters authentifiziert ist.

Das Vorhandensein von mehr als einem Authentifizierungsfilter hängt mit dem Vorhandensein von mehr als einem Authentifizierungsanbieter zusammen, erzwingen Sie dies jedoch nicht. In dem Fall, den ich zuvor offengelegt habe, habe ich zwei Authentifizierungsfilter, aber ich habe nur einen Authentifizierungsanbieter, da beide Filter den gleichen Typ von Authentifizierungsobjekt erstellen, sodass der Authentifizierungsmanager ihn in beiden Fällen an denselben Anbieter delegiert.

Im Gegensatz dazu habe ich auch ein Szenario, in dem ich nur einen UsernamePasswordAuthenticationFilter veröffentliche, die Benutzeranmeldeinformationen jedoch beide in DB oder LDAP enthalten sein können. Ich habe also zwei Anbieter, die UsernamePasswordAuthenticationToken unterstützen, und der AuthenticationManager delegiert jeden Authentifizierungsversuch vom Filter an die Anbieter um die Anmeldeinformationen zu validieren.

Ich denke also, es ist klar, dass weder die Anzahl der Authentifizierungsfilter die Anzahl der Authentifizierungsanbieter noch die Anzahl der Anbieter die Anzahl der Filter bestimmen.

Außerdem wird in der Dokumentation angegeben, dass SecurityContextPersistenceFilter für die Bereinigung des SecurityContext verantwortlich ist, was aufgrund von Thread-Pooling wichtig ist. Wenn ich es weglasse oder eine benutzerdefinierte Implementierung bereitstelle, muss ich die Bereinigung manuell durchführen, oder? Gibt es ähnliche Fallstricke beim Anpassen der Kette?

Ich habe mir diesen Filter vorher nicht genau angesehen, aber nach Ihrer letzten Frage habe ich seine Implementierung überprüft, und wie gewöhnlich im Frühjahr 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, und dabei auf das bewährte Verhalten zu vertrauen, anstatt alles von Grund auf neu zu erstellen.

148
jlumietu

UsernamePasswordAuthenticationFilter wird nur für /login verwendet, und letztere Filter nicht?

Nein, UsernamePasswordAuthenticationFilter erweitert AbstractAuthenticationProcessingFilter und enthält ein RequestMatcher, dh Sie können Ihre eigene Verarbeitungs-URL definieren. Dieser Filter behandelt nur die RequestMatcher Übereinstimmungen mit Anforderungs-URL, die Standard-Verarbeitungs-URL ist /login.

Spätere Filter können die Anforderung weiterhin verarbeiten, wenn UsernamePasswordAuthenticationFilterchain.doFilter(request, response); ausführt.

Weitere Details zu Core Fitlers

Konfiguriert das Namespace-Element für die Formularanmeldung diese Filter automatisch?

UsernamePasswordAuthenticationFilter wird von <form-login> erstellt. Dies sind Standardfilter-Aliase und -Ordnung

Erreicht jede (authentifizierte oder nicht authentifizierte) Anfrage den FilterSecurityInterceptor für eine nicht angemeldete URL?

Es hängt davon ab, ob die Before-Fitler erfolgreich sind, aber FilterSecurityInterceptor ist normalerweise der letzte Fitler.

Werden durch die Konfiguration von zwei http-Elementen zwei springSecurityFitlerChains erstellt?

Ja, jede fitlerChain hat ein RequestMatcher. Wenn das RequestMatcher mit der Anfrage übereinstimmt, wird die Anfrage von den Fitlern in der Fitler-Kette bearbeitet.

Der Standardwert RequestMatcher entspricht allen Anforderungen, wenn Sie das Muster nicht konfigurieren, oder Sie können die spezifische URL konfigurieren (<http pattern="/rest/**").

Wenn Sie mehr über die Fitler erfahren möchten, können Sie den Quellcode in spring security überprüfen. doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)

4
chaoluo