33

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

Richard
  • 5,840
  • 36
  • 123
  • 208
  • 2
    it depends on your application config, basically you can use @MockUser from spring security to fix if you are not make your api `stateless`, otherwise you need adjust your security config not to be `stateless`, then you can use the annotation – Jaiwo99 Jun 09 '17 at 21:51
  • Can you post a minimal project, say in Github? – ngreen Jun 12 '17 at 16:28
  • Just curious. Why do you want to disable security ? It should be part of integration test. Have you thought about mocking or using in-memory set up for your needs ? – s7vr Jun 16 '17 at 20:32
  • @Veeram can you explain the in-memory set up? – Richard Jun 16 '17 at 23:18

10 Answers10

23

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

Danylo Zatorsky
  • 5,856
  • 2
  • 25
  • 49
  • In case you have added an actuator you could try to exclude this as well: org.springframework.boot.actuate.autoconfigure.ManagementWebSecurityAutoConfiguration – Danylo Zatorsky Jun 13 '17 at 19:21
  • This still didn't work. We're using `org.springframework.security.authentication.AuthenticationProvider`, does that change anything? I made edits above – Richard Jun 16 '17 at 23:14
  • In this case, you could try mocking AuthenticationPr‌​ovider as described here: http://tuhrig.de/a-mocked-spring-security-configuration-for-testing/ – Danylo Zatorsky Jun 17 '17 at 22:26
  • 1
    For WebFlux, you will need to exclude another set of class, e.g.: org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration.class – ccppjava Mar 07 '19 at 10:01
6

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
        );
    }
}
s7vr
  • 73,656
  • 11
  • 106
  • 127
  • so should I edit the `WebSecurityConfig` in the main directory or should I create a new `WebSecurityConfig` file in the test directory? – Richard Jun 19 '17 at 21:14
  • You have to edit `WebSecurityConfig` the main directory to include the above changes. This should create specified test user and register with in-memory auth provider and configure basic authentication. Its not at least possible for me to create a test security config to override the main config. Also consider the mock solution too ( does everything and you don't need to edit the main config) . – s7vr Jun 19 '17 at 21:17
  • if I'm reading this code right, there should be no need for the security configuration, the pojo or the controller because you're just setting the token to `1a3434a`, so I can just pass the header "Basic 1a3434a". – Richard Jun 20 '17 at 14:13
  • The basic token is generated from your user and password which is need for basic auth. Its not same as what you get from the authentication token. I was just demonstrating the authentication endpoint workflow works and we get the authentication token for the user. I'm not sure what your authentication endpoint is doing. The returned token from controller is used for authorization to be passed as `Bearer` header and has to be valid for authorization server to allow you gain access to resource endpoint. – s7vr Jun 20 '17 at 14:18
4

It looks like default security configuration are getting kicked-in. Unless I see your your complete configuration it is hard to confirm this. If possible, could you post the your minimal project (on github?).

Since you do not want to enforce the authentication during executions of integration tests, you may enable the anonymous access of your application resources.

To enable anonymous access, you may add below class under your test source directory. It will configure the anonymous access during bootstrapping the web application. (should not see 401 response code)

@Configuration
public class AllowAnonymousWebAccess extends WebSecurityConfigurerAdapter {
    @Override
    public void configure(HttpSecurity web) throws Exception {
        web.antMatcher("**/*").anonymous();
    }
}
skadya
  • 4,330
  • 19
  • 27
  • I tried doing this but now the integration test won't even fully startup. I've made an edit above. – Richard Jun 18 '17 at 21:51
  • Thanks, it helped me, but still curious about how to exclude security completely – nani21984 Oct 01 '18 at 09:55
  • 2
    Works for me. Consider to use `@TestConfiguration` so it won't be picked up during normal start. Further I prefere to put this inside the test class as inner static class. – Torsten Jan 10 '20 at 10:51
4

Was facing this issue for a long time. Finally got it resolved. You need to mock the Authorization server creating a test profile and will also need to Mock Spring Security user details service. Here is the code that I found in a blog.

Test Authorization server

@Configuration
@EnableAuthorizationServer
@ActiveProfiles("test")
public class AuthorizationTestServer extends AuthorizationServerConfigurerAdapter {

    private AuthenticationManager authenticationManager;


    @Autowired
    public AuthorizationTestServer(AuthenticationManager authenticationManager) {
        this.authenticationManager = authenticationManager;
    }

    @Override
    public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
        oauthServer.checkTokenAccess("permitAll()");
        oauthServer.allowFormAuthenticationForClients();
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        super.configure(clients);
        clients.inMemory()
                .withClient("user")
                .secret("password")
                .authorizedGrantTypes("password")
                .scopes("openid");
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        super.configure(endpoints);
        endpoints.authenticationManager(this.authenticationManager);
    }
}

Test User Details Service

@Service
@ActiveProfiles("test")
public class UserDetailTestService implements UserDetailsService {

    @Override
    public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {

            return new User("dummyUser","dummyPassword",true,true,
                    true,true, AuthorityUtils.createAuthorityList("USER"));
    }
}

Main Test class

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@ActiveProfiles("test")
@AutoConfigureMockMvc
public class JmStudentServiceApplicationTests {

   @Autowired
   private WebApplicationContext wac;

   @Autowired
   private MockMvc mockMvc;


   @Autowired
   private TestRestTemplate restTemplate;

   @Autowired
   private StudentRepository studentRepository;

   @Test
   public void test() throws Exception{

      String accessToken = obtainAccessToken("dummyUser", "dummyPassword");
      Student student = new Student();
      student.setId("2222");
      student.setName("test student");

      studentRepository.createStudent(student);
      assertTrue(studentRepository.getStudentById("2222").getName().equals("test student"));

      MvcResult result = mockMvc.perform(get("/students/by-id/2222")
            .header("Authorization", "Bearer " + accessToken)
            .accept(MediaType.APPLICATION_JSON))
            .andExpect(status().isOk())
            .andReturn();


      String str = result.getResponse().getContentAsString();
      assertTrue(str.contains("\"id\":\"2222\""));
   }

   private String obtainAccessToken(String username, String password) throws Exception {

      MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
      params.add("grant_type", "password");
      params.add("username", username);
      params.add("password", password);
      params.add("scope", "openid");

      String base64ClientCredentials = new String(Base64.encodeBase64("user:password".getBytes()));


      ResultActions result
            = mockMvc.perform(post("/oauth/token")
            .params(params)
            .header("Authorization","Basic " + base64ClientCredentials)
            .accept("application/json;charset=UTF-8"))
            .andExpect(status().isOk());

      String resultString = result.andReturn().getResponse().getContentAsString();

      JacksonJsonParser jsonParser = new JacksonJsonParser();
      return jsonParser.parseMap(resultString).get("access_token").toString();
   }

}
Bablu
  • 51
  • 1
3

For bypassing basic authentication while testing With @SpringBootTest and TestRestTemplate, we can chain .withBasicAuth("admin", "password") to template, see below

@Test
    public void givenAuthRequestOnPrivateService_shouldSucceedWith200() throws Exception {
        ResponseEntity<String> result = template.withBasicAuth("spring", "secret")
          .getForEntity("/private/hello", String.class);
        assertEquals(HttpStatus.OK, result.getStatusCode());
    }

for more info see https://www.baeldung.com/spring-security-integration-tests

Ari
  • 153
  • 2
  • 6
1

It looks like authentication does work, but you handle the response in wrong way.

Here's the code below, where you're trying to parse response as List<Foo>

ResponseEntity response = template.exchange("/get",
    HttpMethod.GET,
    request,
    new ParameterizedTypeReference<List<Foo>>() {}
);

But since you haven't provided authentication header, the server responses with some custom error (obviously wrapped into Json Object) and you get this exception in the test saying it can't parse ArrayList from Json Object (which starts with START_OBJECT token, like {).

Could not read JSON document: Can not deserialize instance of java.util.ArrayList out of START_OBJECT token

Try to handle the response as Object so you can see what's actually comes there.

template.exchange("/get", HttpMethod.GET, request, Object.class);

But this won't work as a final solution. I believe you should handle the response body based on Http response code, if that's 200 - parse as List<>, otherwise parse as Map<> or whatever structure the server returns.

matthieusb
  • 505
  • 1
  • 10
  • 34
ikryvorotenko
  • 1,393
  • 2
  • 16
  • 27
  • i'm not sure that this is the case. When I debug, I put a breakpoint at the start of the controller method and it doesn't even get hit. – Richard Jun 16 '17 at 23:03
  • @Richard that's right, the request is handled before it comes to controller by Auth filter. Try to change `new ParameterizedTypeReference>() {}` to `Object.class` and put a breakpoint there. – ikryvorotenko Jun 17 '17 at 05:51
  • Yup I tried that and still no luck. I do get a different response now but it still doesn't even go to the controller. I made my edit above. I can't get past the security layer. – Richard Jun 17 '17 at 13:37
  • @Richard if you have configured security then what do you expect if you don't use authentication header? It is expected behavior that your controller doesn't invoked by your request as security filters reject your unauthorized request. Can you explain what do you try to reach ? Do you need to disable security for tests? – Andriy Rymar Jun 19 '17 at 14:24
1

Why Security Configuration is Enforced

According to the Spring Boot documentation, when you annotate your class with @SpringBootTest, and you do not have specify a configuration alternative, then Spring with search for a @SpringBootApplication class to serve as your primary configuration. Spring starts the search in the package of your test class, then searches up the package hiearchy. Presumably, it is finding your primary configuration, and everything this brings with it including your unwanted security configuration.

Solution

The simplest solution, verified in Spring Boot 2.0.3, is to change @EnableAutoConfiguration(exclude = SecurityAutoConfiguration.class) to @SpringBootApplication(exclude = SecurityAutoConfiguration.class). When you make this change Spring will register your test class as the primary configuration class and will therefore acknowledge your exclusion. Alternatively, create a separate configuration class to share across all your integration tests that resides in a base package and will be found by all your integration tests.

The Gilbert Arenas Dagger
  • 12,071
  • 13
  • 66
  • 80
0

I figured out how to do this without in-memory authentication and/or mocking.

public class TestConf extends WebSecurityConfigurerAdapter {
    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.csrf()
                .disable()
                .authorizeRequests()
                .anyRequest()
                .permitAll();
    }
}

And use spring active profiles to run the above config only when running test cases.

Richard
  • 5,840
  • 36
  • 123
  • 208
0

When @WebMvcTest is used for testing, initializing excludeAutoConfiguration parameter disables Spring Security.

@WebMvcTest(controllers = MyController.class, 
            excludeAutoConfiguration = SecurityAutoConfiguration.class)

Or

When @SpringBootTest is used with @AutoConfigureMockMvc, setting addFilters parameter to false also disables Spring Security.

@SpringBootTest(classes = MyController.class)
@AutoConfigureMockMvc(addFilters = false)
burak isik
  • 395
  • 5
  • 6
0

Simple method :

SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken("username", "password"));

Then you can still use the principal as you would if you where logged in.

Jp Silver
  • 120
  • 9