Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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

like image 437
David Avatar asked Jan 24 '17 09:01

David


3 Answers

@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.

like image 84
Jochen Christ Avatar answered Nov 17 '22 02:11

Jochen Christ


I had de same issue, and the only way I found was creating a token and using it in the mockMvc perform

mockMvc.perform(get("/resource")
                    .with(oAuthHelper.bearerToken("test"))

And the OAuthHelper:

@Component
@EnableAuthorizationServer
public class OAuthHelper extends AuthorizationServerConfigurerAdapter {

    @Autowired
    AuthorizationServerTokenServices tokenservice;

    @Autowired
    ClientDetailsService clientDetailsService;

    public RequestPostProcessor bearerToken(final String clientid) {
        return mockRequest -> {
            OAuth2AccessToken token = createAccessToken(clientid);
            mockRequest.addHeader("Authorization", "Bearer " + token.getValue());
            return mockRequest;
        };
    }

    OAuth2AccessToken createAccessToken(final String clientId) {
        ClientDetails client = clientDetailsService.loadClientByClientId(clientId);
        Collection<GrantedAuthority> authorities = client.getAuthorities();
        Set<String> resourceIds = client.getResourceIds();
        Set<String> scopes = client.getScope();

        Map<String, String> requestParameters = Collections.emptyMap();
        boolean approved = true;
        String redirectUrl = null;
        Set<String> responseTypes = Collections.emptySet();
        Map<String, Serializable> extensionProperties = Collections.emptyMap();

        OAuth2Request oAuth2Request = new OAuth2Request(requestParameters, clientId, authorities,
                approved, scopes, resourceIds, redirectUrl, responseTypes, extensionProperties);

        User userPrincipal = new User("user", "", true, true, true, true, authorities);
        UsernamePasswordAuthenticationToken authenticationToken =
                new UsernamePasswordAuthenticationToken(userPrincipal, null, authorities);
        OAuth2Authentication auth = new OAuth2Authentication(oAuth2Request, authenticationToken);

        return tokenservice.createAccessToken(auth);
    }

    @Override
    public void configure(final ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
                .withClient("test")
                .authorities("READ");
    }

}
like image 32
Laureano Gabriel Clausi Avatar answered Nov 17 '22 01:11

Laureano Gabriel Clausi


As I was specifically trying to write tests against our ResourceServerConfiguration, I worked around the issue by creating a test wrapper for it which set security.stateless to false:

@Configuration
@EnableResourceServer
public class ResourceServerTestConfiguration extends ResourceServerConfigurerAdapter {
  private ResourceServerConfiguration configuration;

  public ResourceServerTestConfiguration(ResourceServerConfiguration configuration) {
    this.configuration = configuration;
  }

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

  @Override
  public void configure(HttpSecurity http) throws Exception {
    configuration.configure(http);
  }
}
like image 1
piepera Avatar answered Nov 17 '22 02:11

piepera