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?
A resource server validates such a token by making a call to the authorisation server's introspection endpoint. The token encodes the entire authorisation in itself and is cryptographically protected against tampering. JSON Web Token (JWT) has become the defacto standard for self-contained tokens.
What Is a Resource Server? In the context of OAuth 2.0, a resource server is an application that protects resources via OAuth tokens. These tokens are issued by an authorization server, typically to a client application. The job of the resource server is to validate the token before serving a resource to the client.
Updated on 17 June, 2022 in Spring Security. Resource Server in OAuth2 is used to protect access to resources, APIs. It will validate the access token passed by the Client Application, with the Authorization Server to decide if the Client Application has access to the resources and APIs it wants.
I found a much easier way to do this following directions I read here: http://docs.spring.io/spring-security/site/docs/4.0.x/reference/htmlsingle/#test-method-withsecuritycontext. This solution is specific to testing @PreAuthorize
with #oauth2.hasScope
but I'm sure it could be adapted for other situations as well.
I create an annotation which can be applied to @Test
s:
WithMockOAuth2Scope
import org.springframework.security.test.context.support.WithSecurityContext; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @Retention(RetentionPolicy.RUNTIME) @WithSecurityContext(factory = WithMockOAuth2ScopeSecurityContextFactory.class) public @interface WithMockOAuth2Scope { String scope() default ""; }
WithMockOAuth2ScopeSecurityContextFactory
import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.oauth2.provider.OAuth2Authentication; import org.springframework.security.oauth2.provider.OAuth2Request; import org.springframework.security.test.context.support.WithSecurityContextFactory; import java.util.HashSet; import java.util.Set; public class WithMockOAuth2ScopeSecurityContextFactory implements WithSecurityContextFactory<WithMockOAuth2Scope> { @Override public SecurityContext createSecurityContext(WithMockOAuth2Scope mockOAuth2Scope) { SecurityContext context = SecurityContextHolder.createEmptyContext(); Set<String> scope = new HashSet<>(); scope.add(mockOAuth2Scope.scope()); OAuth2Request request = new OAuth2Request(null, null, null, true, scope, null, null, null, null); Authentication auth = new OAuth2Authentication(request, null); context.setAuthentication(auth); return context; } }
Example test using MockMvc
:
@RunWith(SpringJUnit4ClassRunner.class) @SpringBootTest public class LoadScheduleControllerTest { private MockMvc mockMvc; @Autowired LoadScheduleController loadScheduleController; @Before public void setup() { mockMvc = MockMvcBuilders.standaloneSetup(loadScheduleController) .build(); } @Test @WithMockOAuth2Scope(scope = "dataLicense") public void testSchedule() throws Exception { mockMvc.perform(post("/schedule").contentType(MediaType.APPLICATION_JSON_UTF8).content(json)).andDo(print()); } }
And this is the controller under test:
@RequestMapping(value = "/schedule", method = RequestMethod.POST) @PreAuthorize("#oauth2.hasScope('dataLicense')") public int schedule() { return 0; }
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
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