I'm trying to run an integration test for my controller but I am running into issues if I don't authenticate. Here's my controller:
@RunWith(SpringRunner.class) @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @TestPropertySource(properties = {"security.basic.enabled=false", "management.security.enabled=false"}) @EnableAutoConfiguration(exclude = {org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration.class}) public class HelloControllerIT { private final ObjectMapper mapper = new ObjectMapper(); @Autowired private TestRestTemplate template; @Test public void test1() throws Exception { ObjectNode loginRequest = mapper.createObjectNode(); loginRequest.put("username","name"); loginRequest.put("password","password"); JsonNode loginResponse = template.postForObject("/authenticate", loginRequest.toString(), JsonNode.class); HttpHeaders headers = new HttpHeaders(); headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON)); headers.add("X-Authorization", "Bearer " + loginResponse.get("token").textValue()); headers.add("Content-Type", "application/json"); return new HttpEntity<>(null, headers); HttpEntity request = getRequestEntity(); ResponseEntity response = template.exchange("/get", HttpMethod.GET, request, new ParameterizedTypeReference<List<Foo>>() {}); //assert stuff } } When I run this, everything works. But if I comment out the line:
headers.add("X-Authorization", "Bearer " + loginResponse.get("token").textValue()); I get the error:
org.springframework.http.converter.HttpMessageNotReadableException: Could not read JSON document: Can not deserialize instance of java.util.ArrayList out of START_OBJECT token at [Source: java.io.PushbackInputStream@272a5bc6; line: 1, column: 1]; nested exception is com.fasterxml.jackson.databind.JsonMappingException: Can not deserialize instance of java.util.ArrayList out of START_OBJECT token at [Source: java.io.PushbackInputStream@272a5bc6; line: 1, column: 1] at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.readJavaType(AbstractJackson2HttpMessageConverter.java:234) at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.read(AbstractJackson2HttpMessageConverter.java:219) at org.springframework.web.client.HttpMessageConverterExtractor.extractData(HttpMessageConverterExtractor.java:95) at org.springframework.web.client.RestTemplate$ResponseEntityResponseExtractor.extractData(RestTemplate.java:917) at org.springframework.web.client.RestTemplate$ResponseEntityResponseExtractor.extractData(RestTemplate.java:901) at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:655) at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:613) at org.springframework.web.client.RestTemplate.exchange(RestTemplate.java:559) at org.springframework.boot.test.web.client.TestRestTemplate.exchange(TestRestTemplate.java:812) at com.test.HelloControllerIT.test1(HelloControllerIT.java:75) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50) at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47) at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17) at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26) at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:75) at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:86) at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84) at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:252) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:94) at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290) at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71) at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288) at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58) at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268) at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61) at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70) at org.junit.runners.ParentRunner.run(ParentRunner.java:363) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:191) at org.junit.runner.JUnitCore.run(JUnitCore.java:137) at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68) at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:51) at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242) at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70) Caused by: com.fasterxml.jackson.databind.JsonMappingException: Can not deserialize instance of java.util.ArrayList out of START_OBJECT token at [Source: java.io.PushbackInputStream@272a5bc6; line: 1, column: 1] at com.fasterxml.jackson.databind.JsonMappingException.from(JsonMappingException.java:270) at com.fasterxml.jackson.databind.DeserializationContext.reportMappingException(DeserializationContext.java:1234) at com.fasterxml.jackson.databind.DeserializationContext.handleUnexpectedToken(DeserializationContext.java:1122) at com.fasterxml.jackson.databind.DeserializationContext.handleUnexpectedToken(DeserializationContext.java:1075) at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.handleNonArray(CollectionDeserializer.java:338) at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:269) at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:259) at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:26) at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:3798) at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:2922) at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.readJavaType(AbstractJackson2HttpMessageConverter.java:231) ... 38 more Obviously the security annotations at the top are not working. So what exactly is the issue and how do I fix it?
Edit 1: I tried doing:
Object response = template.exchange("/get", HttpMethod.GET, request, Object.class); And got:
<401 Unauthorized,{status=401, message=Authentication failed, errorCode=10, timestamp=1497654855545},{X-Content-Type-Options=[nosniff], X-XSS-Protection=[1; mode=block], Cache-Control=[no-cache, no-store, max-age=0, must-revalidate], Pragma=[no-cache], Expires=[0], X-Frame-Options=[DENY], Content-Type=[application/json;charset=ISO-8859-1], Content-Length=[89], Date=[Fri, 16 Jun 2017 23:14:15 GMT]}> For our security we're using org.springframework.security.authentication.AuthenticationProvider and org.springframework.security.authentication.AuthenticationManager
Edit 2: Per skadya's suggestion I created a new class like so:
@Configuration public class AnonymousConfig extends WebSecurityConfigurerAdapter { @Override public void configure(HttpSecurity web) throws Exception { web.antMatcher("**/*").anonymous(); } } But now when I run my integration test I get the following error:
java.lang.IllegalStateException: Failed to load ApplicationContext at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:124) at org.springframework.test.context.support.DefaultTestContext.getApplicationContext(DefaultTestContext.java:83) at org.springframework.boot.test.autoconfigure.SpringBootDependencyInjectionTestExecutionListener.prepareTestInstance(SpringBootDependencyInjectionTestExecutionListener.java:47) at org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:230) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.createTest(SpringJUnit4ClassRunner.java:228) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner$1.runReflectiveCall(SpringJUnit4ClassRunner.java:287) at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.methodBlock(SpringJUnit4ClassRunner.java:289) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:247) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:94) at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290) at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71) at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288) at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58) at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268) at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61) at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70) at org.junit.runners.ParentRunner.run(ParentRunner.java:363) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:191) at org.junit.runner.JUnitCore.run(JUnitCore.java:137) at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68) at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:51) at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242) at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70) Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration': Injection of autowired dependencies failed; nested exception is java.lang.IllegalStateException: @Order on WebSecurityConfigurers must be unique. Order of 100 was already used on config.AnonymousConfig$$EnhancerBySpringCGLIB$$ba18b8d7@6291f725, so it cannot be used on security.WebSecurityConfig$$EnhancerBySpringCGLIB$$9d88e7e@1bfaaae1 too. at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:372) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1264) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:553) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:483) at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:306) at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230) at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:302) at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197) at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:761) at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:866) at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:542) at org.springframework.boot.context.embedded.EmbeddedWebApplicationContext.refresh(EmbeddedWebApplicationContext.java:122) at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:737) at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:370) at org.springframework.boot.SpringApplication.run(SpringApplication.java:314) at org.springframework.boot.test.context.SpringBootContextLoader.loadContext(SpringBootContextLoader.java:120) at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContextInternal(DefaultCacheAwareContextLoaderDelegate.java:98) at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:116) ... 23 more Caused by: java.lang.IllegalStateException: @Order on WebSecurityConfigurers must be unique. Order of 100 was already used on config.AnonymousConfig$$EnhancerBySpringCGLIB$$ba18b8d7@6291f725, so it cannot be used on security.WebSecurityConfig$$EnhancerBySpringCGLIB$$9d88e7e@1bfaaae1 too. at org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration.setFilterChainProxySecurityConfigurer(WebSecurityConfiguration.java:148) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredMethodElement.inject(AutowiredAnnotationBeanPostProcessor.java:701) at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:88) at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:366) ... 40 more Looks like it's clashing with the websecurity config we have in the normal project. Here's that file:
@EnableWebSecurity @EnableWebMvc @EnableGlobalMethodSecurity(prePostEnabled = true) public class WebSecurityConfig extends WebSecurityConfigurerAdapter { //configuration } I tried adding @Order(1000) which fixed the above issue but still ended up in a 401 Unauthorized
You can try excluding few more auto configurations:
@EnableAutoConfiguration(exclude = { org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration.class, org.springframework.boot.autoconfigure.security.SecurityFilterAutoConfiguration.class, org.springframework.boot.autoconfigure.security.FallbackWebSecurityAutoConfiguration.class, org.springframework.boot.autoconfigure.security.oauth2.OAuth2AutoConfiguration.class }) Btw, more elegant way of excluding stuff is by defining application-test.properties in your test sources and marking your test with @Profile("test"). Then just add this to your config:
spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration,org.springframework.boot.autoconfigure.security.SecurityFilterAutoConfiguration,org.springframework.boot.autoconfigure.security.FallbackWebSecurityAutoConfiguration,org.springframework.boot.autoconfigure.security.oauth2.OAuth2AutoConfiguration All the possible configurations that can be excluded you can find here: spring.factories
You have couple of options to provide authentication in the spring boot integration test. You may need to adjust a few things to make it all work at your end.
Mock based approach
This uses test WebApplicationContext injected into MockMvc with @WithMockUser annotation to provide authentication user and WithMockUserSecurityContextFactory creating the security context for the mock user.
SecurityMockMvcConfigurers registers the security filter springSecurityFilterChain with MockMvc.
import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.http.MediaType; import org.springframework.security.test.context.support.WithMockUser; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.web.context.WebApplicationContext; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.notNullValue; import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; @RunWith(SpringRunner.class) @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK) public class HelloControllerIT { @Autowired private WebApplicationContext context; private MockMvc mvc; @Before public void setup() { mvc = MockMvcBuilders .webAppContextSetup(context) .apply(springSecurity()) // enable security for the mock set up .build(); } @WithMockUser(value = "test", password = "pass") @Test public void test() throws Exception { String contentType = MediaType.APPLICATION_JSON + ";charset=UTF-8"; String authzToken = mvc .perform( post("/authenticate") .contentType( MediaType.APPLICATION_JSON). content("")). andExpect(status().isOk()) .andExpect(content().contentType(contentType)) .andExpect(jsonPath("$.token", is(notNullValue()))) .andReturn().getResponse().getContentAsString(); System.out.print(authzToken);//{"token":"1a3434a"} } } In-memory auth provider based approach
This uses in-memory auth provider with basic authentication user.
Register in-memory auth provider and enable basic auth, disable anonymous access in HttpSecurity in the WebSecurityConfigurerAdapter.
When in-memory provider is registered, DefaultInMemoryUserDetailsManagerConfigurer creates the basic auth user in the memory.
When basic authentication is enabled, HttpBasicConfigurer configures BasicAuthenticationFilter. Authenticates the test user and creates the security context.
Security Configuration
@EnableWebSecurity @EnableWebMvc @EnableGlobalMethodSecurity(prePostEnabled = true) public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Override public void configure(AuthenticationManagerBuilder auth) throws Exception { // register test user with in memory authentication provider auth.inMemoryAuthentication().withUser("test").password("pass").roles("ROLES"); } @Override public void configure(HttpSecurity http) throws Exception { // enable basic authentication & disable anoymous access http.authorizeRequests().anyRequest().authenticated().and().httpBasic().and().anonymous().disable(); } } Authentication Endpoint
@Controller @RequestMapping("/authenticate") public class AuthenticationController { @RequestMapping(method = RequestMethod.POST) @ResponseBody public TokenClass getToken() { TokenClass tokenClass = new TokenClass(); tokenClass.setToken("1a3434a"); return tokenClass; } } Pojo
public class TokenClass { private String token; public String getToken() { return token; } public void setToken(String token) { this.token = token; } } Test Controller
import com.fasterxml.jackson.databind.JsonNode; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.web.client.TestRestTemplate; import org.springframework.http.*; import org.springframework.test.context.junit4.SpringRunner; import java.util.Arrays; import java.util.Base64; @RunWith(SpringRunner.class) @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) public class HelloControllerIT { @Autowired private TestRestTemplate template; @Test public void test() throws Exception { HttpHeaders authHeaders = new HttpHeaders(); String token = new String(Base64.getEncoder().encode( ("test" + ":" + "pass").getBytes())); authHeaders.set("Authorization", "Basic " + token); JsonNode loginResponse = template.postForObject("/authenticate", new HttpEntity<>(null, authHeaders), JsonNode.class); HttpHeaders authzHeaders = new HttpHeaders(); authzHeaders.setAccept(Arrays.asList(MediaType.APPLICATION_JSON)); authzHeaders.add("X-Authorization", "Bearer " + loginResponse.get("token").textValue()); authzHeaders.add("Content-Type", "application/json"); ResponseEntity response = template.exchange("/secure", HttpMethod.GET, new HttpEntity<>(null, authzHeaders), String.class ); } }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With