Moja firma oceniała Spring MVC, aby określić, czy powinniśmy go użyć w jednym z naszych następnych projektów. Jak dotąd podoba mi się to, co widziałem, a teraz przyglądam się modułowi Spring Security, aby określić, czy jest to coś, czego możemy / powinniśmy użyć.
Nasze wymagania dotyczące bezpieczeństwa są dość podstawowe; wystarczy, że użytkownik będzie mógł podać nazwę użytkownika i hasło, aby uzyskać dostęp do niektórych części witryny (na przykład uzyskać informacje o swoim koncie); w witrynie znajduje się kilka stron (często zadawane pytania, pomoc techniczna itp.), do których anonimowy użytkownik powinien mieć dostęp.
W utworzonym przeze mnie prototypie zapisałem obiekt „LoginCredentials” (zawierający tylko nazwę użytkownika i hasło) w sesji dla uwierzytelnionego użytkownika; niektóre kontrolery sprawdzają, czy ten obiekt jest w sesji, na przykład, aby uzyskać odniesienie do nazwy zalogowanego użytkownika. Chcę zamiast tego zastąpić tę rodzimą logikę rozwiązaniem Spring Security, co przyniosłoby przyjemną korzyść w postaci usunięcia wszelkiego rodzaju „jak śledzimy zalogowanych użytkowników?” i „jak uwierzytelniamy użytkowników?” z mojego kontrolera / kodu biznesowego.
Wygląda na to, że Spring Security udostępnia obiekt „kontekstu” (dla każdego wątku), aby móc uzyskać dostęp do informacji o nazwie użytkownika / głównym z dowolnego miejsca w aplikacji ...
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
... co wydaje się bardzo nie-Spring, ponieważ ten obiekt jest w pewnym sensie (globalnym) singletonem.
Moje pytanie brzmi: jeśli jest to standardowy sposób uzyskiwania dostępu do informacji o uwierzytelnionym użytkowniku w Spring Security, jaki jest akceptowany sposób wstrzyknięcia obiektu Authentication do SecurityContext, aby był dostępny dla moich testów jednostkowych, gdy testy jednostkowe wymagają Uwierzytelniony użytkownik?
Czy muszę to połączyć w metodzie inicjalizacji każdego przypadku testowego?
protected void setUp() throws Exception {
...
SecurityContextHolder.getContext().setAuthentication(
new UsernamePasswordAuthenticationToken(testUser.getLogin(), testUser.getPassword()));
...
}
Wydaje się, że jest to zbyt szczegółowe. Czy istnieje prostszy sposób?
Sam SecurityContextHolder
obiekt wydaje się bardzo nie-wiosenny ...
źródło
Po prostu zrób to w zwykły sposób, a następnie wstaw go używając
SecurityContextHolder.setContext()
w swojej klasie testowej, na przykład:Kontroler:
Test:
źródło
Authentication a
dodać w kontrolerze? Jak rozumiem w każdym wywołaniu metody? Czy jest w porządku na „wiosenny sposób” po prostu go dodać zamiast wstrzykiwać?@BeforeEach
(JUnit5) lub@Before
(JUnit 4). Dobra i prosta.Nie odpowiadając na pytanie, jak tworzyć i wstrzykiwać obiekty uwierzytelniania, Spring Security 4.0 zapewnia kilka pożądanych alternatyw, jeśli chodzi o testowanie.
@WithMockUser
Adnotacji pozwala programiście określić mock użytkownika (z organami opcjonalne, nazwę użytkownika, hasło i ról) w zgrabny sposób:Istnieje również możliwość wykorzystania
@WithUserDetails
do emulacjiUserDetails
zwracanego zUserDetailsService
npWięcej szczegółów można znaleźć w rozdziałach @WithMockUser i @WithUserDetails w dokumentacji Spring Security (z której skopiowano powyższe przykłady)
źródło
Masz rację, jeśli chodzi o obawy - statyczne wywołania metod są szczególnie problematyczne w przypadku testów jednostkowych, ponieważ nie można łatwo kpić ze swoich zależności. Pokażę ci, jak pozwolić kontenerowi Spring IoC wykonać za Ciebie brudną robotę, pozostawiając zgrabny, testowalny kod. SecurityContextHolder jest klasą frameworkową i chociaż może być w porządku, aby twój kod bezpieczeństwa niskiego poziomu był z nią powiązany, prawdopodobnie chcesz udostępnić bardziej estetyczny interfejs do komponentów UI (tj. Kontrolerów).
cliff.meyers wspomniał o tym w jeden sposób - utwórz własny typ „Principal” i wstrzyknij instancję konsumentom. Znacznik Spring < aop: scoped-proxy /> wprowadzony w 2.x w połączeniu z definicją komponentu bean zakresu żądania i obsługą metod fabrycznych może być przepustką do najbardziej czytelnego kodu.
To mogłoby działać następująco:
Na razie nic skomplikowanego, prawda? W rzeczywistości większość z tych czynności prawdopodobnie musiałaś już zrobić. Następnie, w kontekście twojego beana, zdefiniuj komponent bean o zakresie żądania, który będzie przechowywać jednostkę główną:
Dzięki magii tagu aop: scoped-proxy, statyczna metoda getUserDetails będzie wywoływana za każdym razem, gdy nadejdzie nowe żądanie HTTP, a wszelkie odniesienia do właściwości currentUser zostaną poprawnie rozwiązane. Teraz testy jednostkowe stają się trywialne:
Mam nadzieję że to pomoże!
źródło
Osobiście po prostu użyłbym Powermock wraz z Mockito lub Easymock do mockowania statycznego SecurityContextHolder.getSecurityContext () w twoim urządzeniu / teście integracji, np.
Trzeba przyznać, że jest tutaj sporo kodu kotłowego, tj. Mockowanie obiektu Authentication, mockowanie SecurityContext w celu zwrócenia Authentication i wreszcie mockowanie SecurityContextHolder w celu uzyskania SecurityContext, jednak jest bardzo elastyczny i pozwala na testy jednostkowe w scenariuszach takich jak null obiekty uwierzytelniania itd. bez konieczności zmiany (nie testowego) kodu
źródło
Użycie statycznego w tym przypadku jest najlepszym sposobem pisania bezpiecznego kodu.
Tak, statyka jest ogólnie zła - ogólnie rzecz biorąc, ale w tym przypadku statystyka jest tym, czego chcesz. Ponieważ kontekst zabezpieczeń kojarzy jednostkę główną z aktualnie uruchomionym wątkiem, najbezpieczniejszy kod uzyskiwałby dostęp do statycznej z wątku tak bezpośrednio, jak to możliwe. Ukrycie dostępu za wstrzykniętą klasą opakowującą zapewnia atakującemu więcej punktów do zaatakowania. Nie potrzebowaliby dostępu do kodu (który mieliby trudności ze zmianą, gdyby jar był podpisany), po prostu potrzebują sposobu na przesłonięcie konfiguracji, co można zrobić w czasie wykonywania lub wrzucić trochę XML do ścieżki klas. Nawet użycie iniekcji adnotacji byłoby możliwe do zastąpienia przez zewnętrzny kod XML. Taki XML mógłby wstrzyknąć działającemu systemowi nieuczciwą jednostkę główną.
źródło
I to samo pytanie sobie ponad tutaj , i właśnie opublikował odpowiedź, że Niedawno znalazłem. Krótka odpowiedź brzmi: wstrzyknij a
SecurityContext
i odwołuj sięSecurityContextHolder
tylko do konfiguracji Springa, aby uzyskać plikSecurityContext
źródło
Generał
W międzyczasie (od wersji 3.2, w roku 2013 dzięki SEC-2298 ) uwierzytelnianie można wprowadzić do metod MVC za pomocą adnotacji @AuthenticationPrincipal :
Testy
W teście jednostkowym możesz oczywiście bezpośrednio wywołać tę metodę. W testach integracyjnych za pomocą
org.springframework.test.web.servlet.MockMvc
możeszorg.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user()
wstrzyknąć użytkownika w następujący sposób:Spowoduje to jednak bezpośrednie wypełnienie SecurityContext. Jeśli chcesz mieć pewność, że użytkownik jest ładowany z sesji w twoim teście, możesz użyć tego:
źródło
Chciałbym przyjrzeć się abstrakcyjnym klasom testowym Springa i obiektom pozorowanym, o których tutaj mowa . Zapewniają skuteczny sposób automatycznego okablowania obiektów zarządzanych przez Spring, ułatwiając testowanie jednostek i integracji.
źródło
Uwierzytelnianie jest właściwością wątku w środowisku serwera w taki sam sposób, jak jest właściwością procesu w systemie operacyjnym. Posiadanie instancji bean do uzyskiwania dostępu do informacji uwierzytelniających byłoby niewygodną konfiguracją i kosztem okablowania bez żadnych korzyści.
Jeśli chodzi o uwierzytelnianie testowe, istnieje kilka sposobów na ułatwienie sobie życia. Moim ulubionym jest tworzenie niestandardowych adnotacji
@Authenticated
i nasłuchiwania wykonywania testów, który nim zarządza. PoszukajDirtiesContextTestExecutionListener
inspiracji.źródło
Po wielu pracach udało mi się odtworzyć pożądane zachowanie. Emulowałem logowanie za pomocą MockMvc. Jest zbyt ciężki dla większości testów jednostkowych, ale pomocny przy testach integracyjnych.
Oczywiście chciałbym zobaczyć te nowe funkcje w Spring Security 4.0, które ułatwią nam testowanie.
źródło