Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring OAuth and Boot Integration Test

What is the best way to run Spring Boot integration tests agains a OAuth Resource server configured web application.

I can think of two theoretical approaches:

  1. Mock the security context in the resource server without acutally calling the Authorization server.
  2. Embed the Authorization server as part of the test and redirect the authentication to it.

I was wondering how others have approach this problem.

like image 912
Jakub Narloch Avatar asked Jul 26 '15 20:07

Jakub Narloch


2 Answers

This answer is very similar to the one provided by Ondrej, but is quite a bit simpler.

Spring Security 4 provides Test support. To use it ensure you have spring-security-test-4.0.2.RELEASE.jar (or newer version on your classpath). You will also want to ensure you are working with spring-test-4.1.0.RELEASE (or newer).

Next you can use MockMvc as the other answer indicates. However, if you setup MockMvc with the following:

import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.*;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
@WebAppConfiguration
public class OAuthTests {

    @Autowired
    private WebApplicationContext context;

    private MockMvc mvc;

    @Before
    public void setup() {
        mvc = MockMvcBuilders
                .webAppContextSetup(context)

                // ADD THIS!!
                .apply(springSecurity())
                .build();
    }

This makes it so

  • You no longer need to worry about running in stateless mode or not
  • It also means you do not need to use apply(springSecurity()) as indicated in the other answer.

In short, you should be able to do something like this:

@Test
@WithSecurityContext('user')
public void performOAuth() throws Exception {
    ...    
    // No need for apply(security())!!
    restParcelMockMvc.perform(get("/api/some-resource"))
        .andExpect(...);
}

I'd encourage you to read through the rest of the Spring Security Testing section of the reference as it provides lots of additional details including how to use custom authentication.

like image 143
Rob Winch Avatar answered Sep 20 '22 21:09

Rob Winch


I use spring security 4.x @WithSecurityContext('user') annotation to create mock SecurityContext with 'user' logged in. Then when calling my REST API using MockMvc I retrieve SecurityContext and attach it to the call.

Like this:

@Test
@Transactional
@WithSecurityContext('user')
public void getAllParcels() throws Exception {
    // Initialize the database

    long size = parcelRepository.count();
    parcelRepository.saveAndFlush(parcel);

    // Get all the parcels
    restParcelMockMvc.perform(get("/api/parcels").with(security()))
        .andExpect(status().isOk())
        .andExpect(content().contentType(MediaType.APPLICATION_JSON))
        .andExpect(jsonPath("$.[" + size + "].id").value(parcel.getId()))
        .andExpect(jsonPath("$.[" + size + "].lot").value(DEFAULT_LOT))
        .andExpect(jsonPath("$.[" + size + "].localName").value(DEFAULT_LOCAL_NAME));
}

where security() is static method:

public static RequestPostProcessor security() {
        return SecurityMockMvcRequestPostProcessors.securityContext(SecurityContextHolder.getContext());
}

So using @WithSecurityContext('user') mock SecurityContext with authenticated user with login 'user' is created for my test method. Then in that method I retrieve this mock SecurityContext and attach it to the REST API call to make my oAuth think user is allready authenticated. It's basically the first approach you suggested in your question.

For this to work you must switch your OAuth to be statefull for the tests. Otherwise it won't work.

ie like this:

@Configuration
public class OAuth2ServerConfiguration {

    @Configuration
    @EnableResourceServer
    protected static class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {

        @Autowired(required = false)
        @Qualifier("oauth2StatelessSecurityContext")
        private Boolean stateless = Boolean.TRUE; // STATEFUL switching for tests!

        @Inject
        private Http401UnauthorizedEntryPoint authenticationEntryPoint;

        @Inject
        private AjaxLogoutSuccessHandler ajaxLogoutSuccessHandler;

        @Override
        public void configure(HttpSecurity http) throws Exception {
            http
                .exceptionHandling()
                .authenticationEntryPoint(authenticationEntryPoint)
                .and()
                .logout()
                .logoutUrl("/api/logout")
                .logoutSuccessHandler(ajaxLogoutSuccessHandler)
                .and()
                .csrf()
                .requireCsrfProtectionMatcher(new AntPathRequestMatcher("/oauth/authorize"))
                .disable()
                .headers()
                .frameOptions().disable().and()
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authorizeRequests()
                .antMatchers("/api/authenticate").permitAll()
                .antMatchers("/api/register").permitAll()
                .antMatchers("/api/logs/**").hasAnyAuthority(AuthoritiesConstants.ADMIN)
                .antMatchers("/api/**").authenticated()
                .antMatchers("/metrics/**").hasAuthority(AuthoritiesConstants.ADMIN)
                .antMatchers("/health/**").hasAuthority(AuthoritiesConstants.ADMIN)
                .antMatchers("/trace/**").hasAuthority(AuthoritiesConstants.ADMIN)
                .antMatchers("/dump/**").hasAuthority(AuthoritiesConstants.ADMIN)
                .antMatchers("/shutdown/**").hasAuthority(AuthoritiesConstants.ADMIN)
                .antMatchers("/beans/**").hasAuthority(AuthoritiesConstants.ADMIN)
                .antMatchers("/configprops/**").hasAuthority(AuthoritiesConstants.ADMIN)
                .antMatchers("/info/**").hasAuthority(AuthoritiesConstants.ADMIN)
                .antMatchers("/autoconfig/**").hasAuthority(AuthoritiesConstants.ADMIN)
                .antMatchers("/env/**").hasAuthority(AuthoritiesConstants.ADMIN)
                .antMatchers("/trace/**").hasAuthority(AuthoritiesConstants.ADMIN)
                .antMatchers("/api-docs/**").hasAuthority(AuthoritiesConstants.ADMIN)
                .antMatchers("/protected/**").authenticated();
        }

        @Override
        public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
            resources.stateless(stateless);
            super.configure(resources);
        }

    }
...

You see my stateless property which gets injected only in tests. In normal run it uses it's default value true (so it's stateless). For tests I declare oauth2StatelessSecurityContext Bean with value false so it turns statefull for tests.

I define this configuration for tests:

@Configuration
public class OAuth2Statefull {

    @Bean
    @Primary       
    public Boolean oauth2StatelessSecurityContext() {
        return Boolean.FALSE;
    }

}

That's how I did it. I hope my explanation is understandable.

like image 41
Ondrej Bozek Avatar answered Sep 22 '22 21:09

Ondrej Bozek