Łańcuch filtrów zabezpieczających Spring to bardzo złożony i elastyczny silnik.
Kluczowe filtry w łańcuchu to (w kolejności)
- SecurityContextPersistenceFilter (przywraca uwierzytelnianie z JSESSIONID)
- UsernamePasswordAuthenticationFilter (przeprowadza uwierzytelnianie)
- ExceptionTranslationFilter (przechwytywanie wyjątków bezpieczeństwa z FilterSecurityInterceptor)
- FilterSecurityInterceptor (może generować wyjątki uwierzytelniania i autoryzacji)
Patrząc na aktualną dokumentację stabilnej wersji 4.2.1 , w sekcji 13.3 Zamawianie filtrów , można zobaczyć całą organizację filtrów w łańcuchu filtrów:
13.3 Zamawianie filtrów
Kolejność definiowania filtrów w łańcuchu jest bardzo ważna. Niezależnie od tego, z jakich filtrów faktycznie korzystasz, kolejność powinna wyglądać następująco:
ChannelProcessingFilter , ponieważ może być konieczne przekierowanie do innego protokołu
SecurityContextPersistenceFilter , więc SecurityContext można skonfigurować w SecurityContextHolder na początku żądania internetowego, a wszelkie zmiany w SecurityContext można skopiować do HttpSession po zakończeniu żądania internetowego (gotowe do użycia z następnym żądaniem sieci Web)
ConcurrentSessionFilter , ponieważ używa funkcji SecurityContextHolder i musi zaktualizować SessionRegistry, aby odzwierciedlić bieżące żądania od jednostki głównej
Mechanizmy przetwarzania uwierzytelniania -
UsernamePasswordAuthenticationFilter , CasAuthenticationFilter ,
BasicAuthenticationFilter itp. - aby element SecurityContextHolder mógł zostać zmodyfikowany tak, aby zawierał prawidłowy token żądania uwierzytelnienia
SecurityContextHolderAwareRequestFilter , jeśli używasz go zainstalować Wiosna Bezpieczeństwa świadomy HttpServletRequestWrapper do kontenera serwletów
JaasApiIntegrationFilter , jeśli JaasAuthenticationToken jest w SecurityContextHolder będzie przetwarzać filterChain jako przedmiot w JaasAuthenticationToken
RememberMeAuthenticationFilter , aby jeśli żaden wcześniejszy mechanizm przetwarzania uwierzytelniania nie zaktualizował SecurityContextHolder, a żądanie przedstawia plik cookie umożliwiający działanie usług zapamiętaj mnie, zostanie tam umieszczony odpowiedni zapamiętany obiekt uwierzytelniania
AnonymousAuthenticationFilter , więc jeśli żaden wcześniejszy mechanizm przetwarzania uwierzytelniania nie zaktualizował SecurityContextHolder, zostanie tam umieszczony obiekt anonimowego uwierzytelniania
ExceptionTranslationFilter , aby złapać wszelkie wyjątki Spring Security, aby można było zwrócić odpowiedź błędu HTTP lub uruchomić odpowiedni AuthenticationEntryPoint
FilterSecurityInterceptor , aby chronić identyfikatory URI sieci Web i zgłaszać wyjątki w przypadku odmowy dostępu
Teraz spróbuję przejść do kolejnych pytań, jedno po drugim:
Nie wiem, jak są używane te filtry. Czy chodzi o to, że w przypadku logowania formularza dostarczonego przez wiosnę, UsernamePasswordAuthenticationFilter jest używany tylko do / login, a ostatnie filtry nie? Czy element przestrzeni nazw logowania do formularza automatycznie konfiguruje te filtry? Czy każde żądanie (uwierzytelnione lub nie) dociera do FilterSecurityInterceptor w przypadku adresu URL bez logowania?
Po skonfigurowaniu <security-http>
sekcji dla każdej z nich musisz podać przynajmniej jeden mechanizm uwierzytelniania. To musi być jeden z filtrów pasujących do grupy 4 w sekcji 13.3 Zamawianie filtrów z dokumentacji Spring Security, do której właśnie się odwołałem.
Oto minimalne obowiązujące zabezpieczenia: element http, który można skonfigurować:
<security:http authentication-manager-ref="mainAuthenticationManager"
entry-point-ref="serviceAccessDeniedHandler">
<security:intercept-url pattern="/sectest/zone1/**" access="hasRole('ROLE_ADMIN')"/>
</security:http>
Robiąc to, te filtry są konfigurowane w proxy łańcucha filtrów:
{
"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"
}
Uwaga: otrzymuję je, tworząc prosty RestController, który @Autowires the FilterChainProxy i zwraca jego zawartość:
@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()){
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;
}
Tutaj mogliśmy zobaczyć, że po prostu deklarując <security:http>
element z jedną minimalną konfiguracją, wszystkie domyślne filtry są uwzględnione, ale żaden z nich nie jest typu uwierzytelniania (czwarta grupa w sekcji 13.3 Kolejność filtrów). Oznacza to więc, że po prostu zadeklarowanie security:http
elementu, SecurityContextPersistenceFilter, ExceptionTranslationFilter i FilterSecurityInterceptor są automatycznie konfigurowane.
W rzeczywistości jeden mechanizm przetwarzania uwierzytelniania powinien być skonfigurowany, a nawet ziarna bezpieczeństwa przestrzeni nazw przetwarzają oświadczenia o tym, zgłaszając błąd podczas uruchamiania, ale można to ominąć dodając atrybut punktu wejścia w <http:security>
Jeśli dodam <form-login>
do konfiguracji basic to w ten sposób:
<security:http authentication-manager-ref="mainAuthenticationManager">
<security:intercept-url pattern="/sectest/zone1/**" access="hasRole('ROLE_ADMIN')"/>
<security:form-login />
</security:http>
Teraz filterChain będzie wyglądał tak:
{
"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"
}
Teraz te dwa filtry org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter i org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter są tworzone i konfigurowane w FilterChainProxy.
A więc teraz pytania:
Czy chodzi o to, że w przypadku logowania formularza dostarczonego przez wiosnę, UsernamePasswordAuthenticationFilter jest używany tylko do / login, a ostatnie filtry nie?
Tak, jest używany do próby zakończenia mechanizmu przetwarzania logowania w przypadku, gdy żądanie jest zgodne z adresem URL UsernamePasswordAuthenticationFilter. Ten adres URL można skonfigurować lub nawet zmienić jego zachowanie, aby pasowało do każdego żądania.
Możesz także mieć więcej niż jeden mechanizm przetwarzania uwierzytelniania skonfigurowany w tym samym FilterchainProxy (na przykład HttpBasic, CAS itp.).
Czy element przestrzeni nazw logowania do formularza automatycznie konfiguruje te filtry?
Nie, element form-login konfiguruje UsernamePasswordAUthenticationFilter, a jeśli nie podasz adresu URL strony logowania, konfiguruje również org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter, który kończy się prostym automatycznie wygenerowanym loginem strona.
Pozostałe filtry są domyślnie automatycznie konfigurowane przez utworzenie <security:http>
elementu bez security:"none"
atrybutu.
Czy każde żądanie (uwierzytelnione lub nie) dociera do FilterSecurityInterceptor w przypadku adresu URL bez logowania?
Każde żądanie powinno do niego dotrzeć, gdyż jest to element, który dba o to, czy żądanie ma prawo dotrzeć do żądanego adresu URL. Ale niektóre z filtrów przetworzonych wcześniej mogą zatrzymać przetwarzanie łańcucha filtrów po prostu nie wywołując FilterChain.doFilter(request, response);
. Na przykład filtr CSRF może zatrzymać przetwarzanie łańcucha filtrów, jeśli żądanie nie ma parametru csrf.
Co zrobić, jeśli chcę zabezpieczyć REST API tokenem JWT, który jest pobierany z loginu? Muszę skonfigurować dwa znaczniki http konfiguracji przestrzeni nazw, prawa? Inny dla / login with UsernamePasswordAuthenticationFilter
, a drugi dla adresów URL REST, z niestandardowym JwtAuthenticationFilter
.
Nie, nie jesteś do tego zmuszony. Możesz zadeklarować oba UsernamePasswordAuthenticationFilter
i the JwtAuthenticationFilter
w tym samym elemencie http, ale zależy to od konkretnego zachowania każdego z tych filtrów. Oba podejścia są możliwe i ostatecznie wybór zależy od własnych preferencji.
Czy skonfigurowanie dwóch elementów http powoduje utworzenie dwóch springSecurityFitlerChains?
Tak to prawda
Czy UsernamePasswordAuthenticationFilter jest domyślnie wyłączony, dopóki nie zadeklaruję logowania przez formularz?
Tak, możesz to zobaczyć w filtrach podniesionych w każdej z opublikowanych przeze mnie konfiguracji
Jak zamienić SecurityContextPersistenceFilter na taki, który uzyska uwierzytelnienie z istniejącego tokenu JWT zamiast JSESSIONID?
Możesz uniknąć SecurityContextPersistenceFilter, po prostu skonfiguruj strategię sesji w <http:element>
. Po prostu skonfiguruj w ten sposób:
<security:http create-session="stateless" >
Lub w tym przypadku możesz nadpisać go innym filtrem, w ten sposób wewnątrz <security:http>
elementu:
<security:http ...>
<security:custom-filter ref="myCustomFilter" position="SECURITY_CONTEXT_FILTER"/>
</security:http>
<beans:bean id="myCustomFilter" class="com.xyz.myFilter" />
EDYTOWAĆ:
Jedno pytanie o „Ty też możesz mieć więcej niż jeden mechanizm przetwarzania uwierzytelniania skonfigurowany w tym samym FilterchainProxy”. Czy ta ostatnia nadpisze uwierzytelnienie przeprowadzone przez pierwsze, jeśli deklaruje się wiele filtrów uwierzytelniania (implementacja Spring)? Jak to się ma do posiadania wielu dostawców uwierzytelniania?
Ostatecznie zależy to od implementacji samego każdego filtru, ale prawdą jest, że te ostatnie filtry uwierzytelniania są w stanie przynajmniej nadpisać wszelkie wcześniejsze uwierzytelnienia, które ostatecznie zostały wykonane przez poprzednie filtry.
Ale to niekoniecznie się stanie. Mam pewne przypadki produkcyjne w zabezpieczonych usługach REST, w których używam pewnego rodzaju tokenu autoryzacyjnego, który można podać zarówno jako nagłówek HTTP, jak i wewnątrz treści żądania. Więc konfiguruję dwa filtry, które odzyskują ten token, w jednym przypadku z nagłówka HTTP, a drugi z treści żądania własnego żądania odpoczynku. Prawdą jest, że jeśli jedno żądanie HTTP zawiera ten token uwierzytelniania zarówno jako nagłówek HTTP, jak i wewnątrz treści żądania, oba filtry będą próbowały wykonać mechanizm uwierzytelniania delegując go do menedżera, ale można go łatwo uniknąć, po prostu sprawdzając, czy żądanie jest już uwierzytelniony na początku doFilter()
metody każdego filtra.
Posiadanie więcej niż jednego filtru uwierzytelniania wiąże się z posiadaniem więcej niż jednego dostawcy uwierzytelniania, ale nie wymuszaj tego. W przypadku ujawnienia wcześniej mam dwa filtry uwierzytelniania, ale mam tylko jednego dostawcę uwierzytelniania, ponieważ oba filtry tworzą ten sam typ obiektu uwierzytelniania, więc w obu przypadkach menedżer uwierzytelniania deleguje go do tego samego dostawcy.
I przeciwnie, ja też mam scenariusz, w którym publikuję tylko jeden UsernamePasswordAuthenticationFilter, ale poświadczenia użytkownika oba mogą być zawarte w DB lub LDAP, więc mam dwóch dostawców obsługujących UsernamePasswordAuthenticationToken, a AuthenticationManager deleguje każdą próbę uwierzytelnienia z filtru do dostawców w celu potwierdzenia poświadczeń.
Myślę więc, że jest jasne, że ani liczba filtrów uwierzytelniania nie określa liczby dostawców uwierzytelniania, ani liczba dostawców nie determinuje liczby filtrów.
Ponadto dokumentacja stwierdza, że SecurityContextPersistenceFilter jest odpowiedzialny za czyszczenie SecurityContext, co jest ważne ze względu na buforowanie wątków. Jeśli go pominę lub wykonam niestandardową implementację, muszę wykonać czyszczenie ręcznie, prawda? Czy istnieje więcej podobnych problemów podczas dostosowywania łańcucha?
Wcześniej nie przyglądałem się dokładnie temu filtrowi, ale po Twoim ostatnim pytaniu sprawdzałem jego implementację i jak zwykle w Spring prawie wszystko można było skonfigurować, rozszerzyć lub nadpisać.
W SecurityContextPersistenceFilter delegaci w SecurityContextRepository realizacji poszukiwanie SecurityContext. Domyślnie używany jest HttpSessionSecurityContextRepository , ale można to zmienić za pomocą jednego z konstruktorów filtru. Więc może być lepiej napisać SecurityContextRepository, który pasuje do twoich potrzeb i po prostu skonfigurować go w SecurityContextPersistenceFilter, ufając jego sprawdzonemu zachowaniu, zamiast zaczynać wszystko od zera.