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
@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.
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");
}
}
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);
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With