20

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

David
  • 260
  • 1
  • 5
  • 13

3 Answers3

29

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

Kieran
  • 5,906
  • 3
  • 24
  • 34
Jochen Christ
  • 697
  • 6
  • 13
  • Adding this class to my (maven project) src/test/java works for me with Spring Boot 1.5.4 and 1.5.9. I can now user @WithMockUser as I expected. – DaShaun Jan 14 '18 at 18:25
  • 1
    Adding this class to my project allows me to bypass security however it doesn't seem like the user matters. It always passes the security regardless of the scope I give my test users. Is that the intent? – Rig Mar 02 '18 at 20:35
  • 1
    Any links to user guides/documentation? – Witold Kaczurba Apr 20 '18 at 10:22
  • how can I do it only for test context? – aycanadal Jan 18 '19 at 12:22
  • 1
    I took the flag from the application.properties and enabled it only for testing: @Value("${oauth.stateless:true}") private boolean oauthstateless; – Fabian Mendez Feb 04 '19 at 16:23
6

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");
    }

}
  • 1
    the problem is that my security service which creates the tokens in production is an other microservice than the one which uses the token to check the authorization and authentication... and since its an JWT token it uses an private key file to create the token and i dont want to have the private token spreaded over all my resource micro services. or am i getting anything wrong? – David Feb 03 '17 at 22:39
  • Yes, I have the same design, so to test the microservice that expose the resources, I did that in the tests – Laureano Gabriel Clausi Feb 06 '17 at 15:57
  • I also have a privete key in the auth service and a key in the other microservice – Laureano Gabriel Clausi Feb 06 '17 at 17:04
  • This worked for me thanks... I had to remove EnableAuthorizationServer though because I only wanted to generate the token manually and without any other thing being loaded – xbmono Jan 11 '18 at 06:37
1

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);
  }
}
piepera
  • 2,033
  • 1
  • 20
  • 21