Use @WithMockUser (with @SpringBootTest) inside an oAuth2 Resource Server Application

David picture David · Jan 24, 2017 · Viewed 20.2k times · Source

Environment: I have a spring boot based microservice architecture application consisting of multiple infrastructural services and resource services (containing the business logic). Authorization and authentication is handled by an oAuth2-Service managing the user entities and creating JWT tokens for the clients.

To test a single microservice application in its entirety i tried to build tests with testNG, spring.boot.test, org.springframework.security.test ...

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK, properties = {"spring.cloud.discovery.enabled=false", "spring.cloud.config.enabled=false", "spring.profiles.active=test"})
@AutoConfigureMockMvc
@Test
public class ArtistControllerTest extends AbstractTestNGSpringContextTests {

  @Autowired
  private MockMvc mvc;

  @BeforeClass
  @Transactional
  public void setUp() {
    // nothing to do
  }

  @AfterClass
  @Transactional
  public void tearDown() {
    // nothing to do here
  }

  @Test
  @WithMockUser(authorities = {"READ", "WRITE"})
  public void getAllTest() throws Exception {

    // EXPECT HTTP STATUS 200
    // BUT GET 401
    this.mvc.perform(get("/")
            .accept(MediaType.APPLICATION_JSON))
            .andExpect(status().isOk())
  }
}

where the security (resource server) config is the following

@Configuration
@EnableResourceServer
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {

  // get the configured token store
  @Autowired
  TokenStore tokenStore;

  // get the configured token converter
  @Autowired
  JwtAccessTokenConverter tokenConverter;

  /**
   * !!! configuration of springs http security !!!
   */
  @Override
  public void configure(HttpSecurity http) throws Exception {
      http
            .csrf().disable()
            .authorizeRequests()
            .antMatchers("/**").authenticated();
  }

  /**
   * configuration of springs resource server security
   */
  @Override
  public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
    // set the configured tokenStore to this resourceServer
    resources.resourceId("artist").tokenStore(tokenStore);
  }

}

and the following method based security check annotated inside the controller class

@PreAuthorize("hasAuthority('READ')")
@RequestMapping(value = "/", method = RequestMethod.GET)
public List<Foo> getAll(Principal user) {
    List<Foo> foos = fooRepository.findAll();
    return foos;
}

I thought that would work but when running the test i only get an assertion error

java.lang.AssertionError: Status 
Expected :200
Actual   :401


Question: Is there something totally obvious that i am doing wrong? Or is @WithMockUser not going to work with @SpringBootTest and @AutoConfigureMockMvc in an oAuth2 environment? If this is the case... what would be the best approach for testing route and method based security configurations as part of such an (integration) test like this one?


Appendix: I also tried different approaches like something like the following... but it led to the same result :(

this.mvc.perform(get("/")
        .with(user("admin").roles("READ","WRITE").authorities(() -> "READ", () -> "WRITE"))
        .accept(MediaType.APPLICATION_JSON))

see:
spring security testing
spring boot 1.4 testing

Answer

Jochen Christ picture Jochen Christ · Jun 12, 2017

@WithMockUser creates the authentication in SecurityContext. Same applies for with(user("username")).

By default the OAuth2AuthenticationProcessingFilter does not use the SecurityContext, but always build the authentication from the token ("stateless").

You can easily change this behavior be setting the stateless flag in the resource server security configuration to false:

@Configuration
@EnableResourceServer
public class ResourceServerConfiguration implements ResourceServerConfigurer {

    @Override
    public void configure(ResourceServerSecurityConfigurer security) throws Exception {
        security.stateless(false);
    }

    @Override
    public void configure(HttpSecurity http) {}

}

Another option is to extend ResourceServerConfigurerAdapter, but the problem with that is that it comes with configuration that forces all requests to be authenticated. Implementing the interface leaves your main security config unchanged apart from the statelessness.

Of course, set the flag to to false in your test contexts, only.