Following the release of Spring Security 4 and it's improved support for testing I've wanted to update my current Spring security oauth2 resource server tests.
At present I have a helper class that sets up a OAuth2RestTemplate
using ResourceOwnerPasswordResourceDetails
with a test ClientId
connecting to an actual AccessTokenUri
to requests a valid token for my tests. This resttemplate is then used to make requests in my @WebIntegrationTest
s.
I'd like to drop the dependency on the actual AuthorizationServer, and the use of valid (if limited) user credentials in my tests, by taking advantage of the new testing support in Spring Security 4.
Up to now all my attempts at using @WithMockUser
, @WithSecurityContext
, SecurityMockMvcConfigurers.springSecurity()
& SecurityMockMvcRequestPostProcessors.*
have failed to make authenticated calls through MockMvc
, and I can not find any such working examples in the Spring example projects.
Can anyone help me test my oauth2 resource server with some kind of mocked credentials, while still testing the security restrictions imposed?
** EDIT ** Sample code available here: https://github.com/timtebeek/resource-server-testing For each of the test classes I understand why it won't work as it, but I'm looking for ways that would allow me to test the security setup easily.
I'm now thinking of creating a very permissive OAuthServer under src/test/java
, which might help a bit. Does anyone have any other suggestions?
To test resource server security effectively, both with MockMvc
and a RestTemplate
it helps to configure an AuthorizationServer
under src/test/java
:
AuthorizationServer
@Configuration
@EnableAuthorizationServer
@SuppressWarnings("static-method")
class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Bean
public JwtAccessTokenConverter accessTokenConverter() throws Exception {
JwtAccessTokenConverter jwt = new JwtAccessTokenConverter();
jwt.setSigningKey(SecurityConfig.key("rsa"));
jwt.setVerifierKey(SecurityConfig.key("rsa.pub"));
jwt.afterPropertiesSet();
return jwt;
}
@Autowired
private AuthenticationManager authenticationManager;
@Override
public void configure(final AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
.authenticationManager(authenticationManager)
.accessTokenConverter(accessTokenConverter());
}
@Override
public void configure(final ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("myclientwith")
.authorizedGrantTypes("password")
.authorities("myauthorities")
.resourceIds("myresource")
.scopes("myscope")
.and()
.withClient("myclientwithout")
.authorizedGrantTypes("password")
.authorities("myauthorities")
.resourceIds("myresource")
.scopes(UUID.randomUUID().toString());
}
}
Integration test
For integration tests one can then simply use built in OAuth2 test support rule and annotions:
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = MyApp.class)
@WebIntegrationTest(randomPort = true)
@OAuth2ContextConfiguration(MyDetails.class)
public class MyControllerIT implements RestTemplateHolder {
@Value("http://localhost:${local.server.port}")
@Getter
String host;
@Getter
@Setter
RestOperations restTemplate = new TestRestTemplate();
@Rule
public OAuth2ContextSetup context = OAuth2ContextSetup.standard(this);
@Test
public void testHelloOAuth2WithRole() {
ResponseEntity<String> entity = getRestTemplate().getForEntity(host + "/hello", String.class);
assertTrue(entity.getStatusCode().is2xxSuccessful());
}
}
class MyDetails extends ResourceOwnerPasswordResourceDetails {
public MyDetails(final Object obj) {
MyControllerIT it = (MyControllerIT) obj;
setAccessTokenUri(it.getHost() + "/oauth/token");
setClientId("myclientwith");
setUsername("user");
setPassword("password");
}
}
MockMvc test
Testing with MockMvc
is also possible, but needs a little helper class to get a RequestPostProcessor
that sets the Authorization: Bearer <token>
header on requests:
@Component
public class OAuthHelper {
// For use with MockMvc
public RequestPostProcessor bearerToken(final String clientid) {
return mockRequest -> {
OAuth2AccessToken token = createAccessToken(clientid);
mockRequest.addHeader("Authorization", "Bearer " + token.getValue());
return mockRequest;
};
}
@Autowired
ClientDetailsService clientDetailsService;
@Autowired
AuthorizationServerTokenServices tokenservice;
OAuth2AccessToken createAccessToken(final String clientId) {
// Look up authorities, resourceIds and scopes based on clientId
ClientDetails client = clientDetailsService.loadClientByClientId(clientId);
Collection<GrantedAuthority> authorities = client.getAuthorities();
Set<String> resourceIds = client.getResourceIds();
Set<String> scopes = client.getScope();
// Default values for other parameters
Map<String, String> requestParameters = Collections.emptyMap();
boolean approved = true;
String redirectUrl = null;
Set<String> responseTypes = Collections.emptySet();
Map<String, Serializable> extensionProperties = Collections.emptyMap();
// Create request
OAuth2Request oAuth2Request = new OAuth2Request(requestParameters, clientId, authorities, approved, scopes,
resourceIds, redirectUrl, responseTypes, extensionProperties);
// Create OAuth2AccessToken
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);
}
}
Your MockMvc
tests must then get a RequestPostProcessor
from the OauthHelper
class and pass it when making requests:
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = MyApp.class)
@WebAppConfiguration
public class MyControllerTest {
@Autowired
private WebApplicationContext webapp;
private MockMvc mvc;
@Before
public void before() {
mvc = MockMvcBuilders.webAppContextSetup(webapp)
.apply(springSecurity())
.alwaysDo(print())
.build();
}
@Autowired
private OAuthHelper helper;
@Test
public void testHelloWithRole() throws Exception {
RequestPostProcessor bearerToken = helper.bearerToken("myclientwith");
mvc.perform(get("/hello").with(bearerToken)).andExpect(status().isOk());
}
@Test
public void testHelloWithoutRole() throws Exception {
RequestPostProcessor bearerToken = helper.bearerToken("myclientwithout");
mvc.perform(get("/hello").with(bearerToken)).andExpect(status().isForbidden());
}
}
A full sample project is available on GitHub:
https://github.com/timtebeek/resource-server-testing