Ich habe versucht herauszufinden, wie ein Unit-Test durchgeführt werden kann, wenn die URLs meiner Controller ordnungsgemäß gesichert sind. Nur für den Fall, dass jemand etwas ändert und versehentlich die Sicherheitseinstellungen entfernt.
Meine Controller-Methode sieht folgendermaßen aus:
@RequestMapping("/api/v1/resource/test")
@Secured("ROLE_USER")
public @ResonseBody String test() {
return "test";
}
Ich habe eine WebTestEnvironment wie folgt eingerichtet:
import javax.annotation.Resource;
import javax.naming.NamingException;
import javax.sql.DataSource;
import org.junit.Before;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.FilterChainProxy;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration({
"file:src/main/webapp/WEB-INF/spring/security.xml",
"file:src/main/webapp/WEB-INF/spring/applicationContext.xml",
"file:src/main/webapp/WEB-INF/spring/servlet-context.xml" })
public class WebappTestEnvironment2 {
@Resource
private FilterChainProxy springSecurityFilterChain;
@Autowired
@Qualifier("databaseUserService")
protected UserDetailsService userDetailsService;
@Autowired
private WebApplicationContext wac;
@Autowired
protected DataSource dataSource;
protected MockMvc mockMvc;
protected final Logger logger = LoggerFactory.getLogger(this.getClass());
protected UsernamePasswordAuthenticationToken getPrincipal(String username) {
UserDetails user = this.userDetailsService.loadUserByUsername(username);
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(
user,
user.getPassword(),
user.getAuthorities());
return authentication;
}
@Before
public void setupMockMvc() throws NamingException {
// setup mock MVC
this.mockMvc = MockMvcBuilders
.webAppContextSetup(this.wac)
.addFilters(this.springSecurityFilterChain)
.build();
}
}
In meinem eigentlichen Test habe ich versucht, so etwas zu tun:
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import org.junit.Test;
import org.springframework.mock.web.MockHttpSession;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
import eu.ubicon.webapp.test.WebappTestEnvironment;
public class CopyOfClaimTest extends WebappTestEnvironment {
@Test
public void signedIn() throws Exception {
UsernamePasswordAuthenticationToken principal =
this.getPrincipal("test1");
SecurityContextHolder.getContext().setAuthentication(principal);
super.mockMvc
.perform(
get("/api/v1/resource/test")
// .principal(principal)
.session(session))
.andExpect(status().isOk());
}
}
Ich habe das hier aufgegriffen:
- http://java.dzone.com/articles/spring-test-mvc-junit-testing hier:
- http://techdive.in/solutions/how-mock-securitycontextholder-perfrom-junit-tests-spring-controller oder hier:
- Wie testet JUnit eine @ PreAuthorize-Annotation und ihre Feder-EL, die von einem Feder-MVC-Controller angegeben werden?
Wenn man genau hinschaut, hilft dies jedoch nur, wenn keine tatsächlichen Anforderungen an URLs gesendet werden, sondern nur, wenn Dienste auf Funktionsebene getestet werden. In meinem Fall wurde eine Ausnahme "Zugriff verweigert" ausgelöst:
org.springframework.security.access.AccessDeniedException: Access is denied
at org.springframework.security.access.vote.AffirmativeBased.decide(AffirmativeBased.java:83) ~[spring-security-core-3.1.3.RELEASE.jar:3.1.3.RELEASE]
at org.springframework.security.access.intercept.AbstractSecurityInterceptor.beforeInvocation(AbstractSecurityInterceptor.java:206) ~[spring-security-core-3.1.3.RELEASE.jar:3.1.3.RELEASE]
at org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor.invoke(MethodSecurityInterceptor.java:60) ~[spring-security-core-3.1.3.RELEASE.jar:3.1.3.RELEASE]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172) ~[spring-aop-3.2.1.RELEASE.jar:3.2.1.RELEASE]
...
Die folgenden zwei Protokollmeldungen besagen im Wesentlichen, dass kein Benutzer authentifiziert wurde, was darauf hinweist, dass die Einstellung Principal
nicht funktioniert hat oder dass sie überschrieben wurde.
14:20:34.454 [main] DEBUG o.s.s.a.i.a.MethodSecurityInterceptor - Secure object: ReflectiveMethodInvocation: public java.util.List test.TestController.test(); target is of class [test.TestController]; Attributes: [ROLE_USER]
14:20:34.454 [main] DEBUG o.s.s.a.i.a.MethodSecurityInterceptor - Previously Authenticated: org.springframework.security.authentication.AnonymousAuthenticationToken@9055e4a6: Principal: anonymousUser; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@957e: RemoteIpAddress: 127.0.0.1; SessionId: null; Granted Authorities: ROLE_ANONYMOUS
Antworten:
Als ich nach einer Antwort suchte, konnte ich nicht feststellen, dass sie gleichzeitig einfach und flexibel war. Dann fand ich die Spring Security Reference und stellte fest, dass es nahezu perfekte Lösungen gibt. AOP - Lösungen oft sind die Größten zum Testen, und Spring bietet es mit
@WithMockUser
,@WithUserDetails
und@WithSecurityContext
in diesem Artefakt:In den meisten Fällen,
@WithUserDetails
sammelt sich die Flexibilität und Kraft, die ich brauche.Wie funktioniert @WithUserDetails?
Grundsätzlich müssen Sie nur eine benutzerdefinierte Version
UserDetailsService
mit allen möglichen Benutzerprofilen erstellen, die Sie testen möchten. Z.BJetzt haben wir unsere Benutzer bereit. Stellen Sie sich vor, wir möchten die Zugriffssteuerung für diese Controller-Funktion testen:
Hier haben wir eine get kartiert Funktion auf die Route / foo / Gruß und testen wir eine rollenbasierte Sicherheit mit der
@Secured
Anmerkung, obwohl Sie testen können@PreAuthorize
und@PostAuthorize
wie gut. Erstellen wir zwei Tests, einen, um zu überprüfen, ob ein gültiger Benutzer diese Grußantwort sehen kann, und einen, um zu überprüfen, ob sie tatsächlich verboten ist.Wie Sie sehen, haben wir importiert
SpringSecurityWebAuxTestConfig
, um unsere Benutzer zum Testen bereitzustellen. Jeder wird für seinen entsprechenden Testfall verwendet, indem nur eine einfache Anmerkung verwendet wird, wodurch Code und Komplexität reduziert werden.Verwenden Sie @WithMockUser besser, um die rollenbasierte Sicherheit zu vereinfachen
Wie Sie sehen,
@WithUserDetails
verfügt es über die Flexibilität, die Sie für die meisten Ihrer Anwendungen benötigen. Sie können benutzerdefinierte Benutzer mit einer beliebigen GrantedAuthority verwenden, z. B. Rollen oder Berechtigungen. Wenn Sie jedoch nur mit Rollen arbeiten, kann das Testen noch einfacher sein und Sie können das Erstellen einer benutzerdefinierten Rolle vermeidenUserDetailsService
. Geben Sie in solchen Fällen eine einfache Kombination aus Benutzer, Kennwort und Rollen mit @WithMockUser an .Die Anmerkung definiert Standardwerte für einen sehr einfachen Benutzer. Da in unserem Fall die Route, die wir testen, nur erfordert, dass der authentifizierte Benutzer ein Manager ist, können wir die Verwendung beenden
SpringSecurityWebAuxTestConfig
und dies tun.Beachten Sie, dass wir jetzt anstelle des Benutzers [email protected] den Standard erhalten, der bereitgestellt wird von
@WithMockUser
: user ; Dennoch spielt es keine Rolle, denn was uns wirklich wichtig ist, ist seine Rolle :ROLE_MANAGER
.Schlussfolgerungen
Wie Sie sehen mit Anmerkungen wie
@WithUserDetails
und@WithMockUser
wir können zwischen verschiedenen authentifizierten Benutzern Szenarien wechseln zur Herstellung von einfachen Tests ohne Gebäudeklassen entfremdet von unserer Architektur einfach. Es wird auch empfohlen, zu sehen, wie @WithSecurityContext für noch mehr Flexibilität funktioniert.quelle
tom
, während die zweite vonjerry
?BasicUser
undManager User
Szenario in der Antwort. Das Schlüsselkonzept ist, dass wir uns nicht um die Benutzer kümmern, sondern um ihre Rollen, aber jeder dieser Tests, die sich im selben Komponententest befinden, repräsentiert tatsächlich unterschiedliche Abfragen. von verschiedenen Benutzern (mit unterschiedlichen Rollen) auf demselben Endpunkt durchgeführt.Seit Spring 4.0+ besteht die beste Lösung darin, die Testmethode mit @WithMockUser zu kommentieren
Denken Sie daran, Ihrem Projekt die folgende Abhängigkeit hinzuzufügen
quelle
Es stellte sich heraus, dass das
SecurityContextPersistenceFilter
, das Teil der Spring Security-Filterkette ist, immer mein zurücksetztSecurityContext
, was ich als Aufruf festgelegt habeSecurityContextHolder.getContext().setAuthentication(principal)
(oder mithilfe der.principal(principal)
Methode). Dieser Filter setzt denSecurityContext
inSecurityContextHolder
mit einemSecurityContext
von einemSecurityContextRepository
OVERWRITING den zuvor eingestellten. Das Repository istHttpSessionSecurityContextRepository
standardmäßig ein. DerHttpSessionSecurityContextRepository
prüft das GegebeneHttpRequest
und versucht auf das entsprechende zuzugreifenHttpSession
. Wenn es existiert, wird versucht, dasSecurityContext
aus dem zu lesenHttpSession
. Wenn dies fehlschlägt, generiert das Repository ein leeresSecurityContext
.Daher besteht meine Lösung darin, eine
HttpSession
mit der Anfrage weiterzuleiten , die Folgendes enthältSecurityContext
:quelle
getPrincipal()
das meiner Meinung nach etwas irreführend ist. idealerweise hätte es benannt werden sollengetAuthentication()
. Ebenso sollte in IhremsignedIn()
Test die lokale Variable benannt werdenauth
oderauthentication
anstelle vonprincipal
In pom.xml hinzufügen:
und
org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors
für Autorisierungsanfragen verwenden. Die Beispielverwendung finden Sie unter https://github.com/rwinch/spring-security-test-blog ( https://jira.spring.io/browse/SEC-2592) ).Aktualisieren:
4.0.0.RC2 arbeitet für die Federsicherheit 3.x. Für Spring-Security 4 werden Spring-Security-Test Teil von Spring-Security ( http://docs.spring.io/spring-security/site/docs/4.0.x/reference/htmlsingle/#test) , Version ist dieselbe ).
Die Einrichtung wurde geändert: http://docs.spring.io/spring-security/site/docs/4.0.x/reference/htmlsingle/#test-mockmvc
Beispiel für die Basisauthentifizierung: http://docs.spring.io/spring-security/site/docs/4.0.x/reference/htmlsingle/#testing-http-basic-authentication .
quelle
Hier ist ein Beispiel für diejenigen, die Spring MockMvc Security Config mithilfe der Base64-Basisauthentifizierung testen möchten.
Maven-Abhängigkeit
quelle
Kurze Antwort:
Nach der Durchführung
formLogin
des Sicherheitstests im Frühjahr wird jede Ihrer Anforderungen automatisch als angemeldeter Benutzer aufgerufen.Lange Antwort:
Überprüfen Sie diese Lösung (die Antwort ist für Spring 4): So melden Sie einen Benutzer mit Spring 3.2 New MVC Testing an
quelle
Optionen zur Vermeidung der Verwendung von SecurityContextHolder in Tests:
SecurityContextHolder
mit einer Mock-Bibliothek - EasyMock zum BeispielSecurityContextHolder.get...
in Ihren Code in einem Dienst - zum BeispielSecurityServiceImpl
mit einer MethodegetCurrentPrincipal
, die dieSecurityService
Schnittstelle implementiert , und dann in Ihren Tests können Sie einfach eine Scheinimplementierung dieser Schnittstelle erstellen, die den gewünschten Principal ohne Zugriff auf zurückgibtSecurityContextHolder
.quelle