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