Reputation: 747
I'm currently using RemoteTokenServices
class:
@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
@Value("${auth-server.url}")
private String authEndpoint;
@Value("${security.oauth2.client.client-id}")
private String clientId;
@Value("${security.oauth2.client.client-secret}")
private String clientSecret;
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.resourceId("ms/legacy");
}
@Bean
public ResourceServerTokenServices tokenService() {
RemoteTokenServices tokenServices = new RemoteTokenServices();
tokenServices.setClientId(clientId);
tokenServices.setClientSecret(clientSecret);
tokenServices.setCheckTokenEndpointUrl(authEndpoint + "/uaa/oauth/check_token");
return tokenServices;
}
}
I want to be able to mock this easily and properly for all my endpoints integration tests, knowing that:
OncePerRequestFilter
to get some crucial infoIs there a standard way to:
The expected result would be that I can write an endpoint test with only a few extra lines to setup the right JWT in the request, and the token service would agree on its validity dumbly.
Upvotes: 1
Views: 1953
Reputation: 747
Given that we don't want to test security at all, the best solution for this kind of case is to:
@WithMockUser
along with MockMvc
ResourceServerConfigurerAdapter
for tests:@ActiveProfiles("!test")
) that hosts the token specific configurationsecurity.stateless(false);
)test
profileHere is how it was implemented in practice:
Base ResourceServerConfigurerAdapter
so that the configuration has a major common part between tests and non-tests contexts:
public class BaseResourceServerConfiguration extends ResourceServerConfigurerAdapter {
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.resourceId("ms/legacy");
}
@Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().permitAll().and().cors().disable().csrf().disable().httpBasic().disable()
.exceptionHandling()
.authenticationEntryPoint(
(request, response, authException) -> response.sendError(HttpServletResponse.SC_UNAUTHORIZED))
.accessDeniedHandler(
(request, response, authException) -> response.sendError(HttpServletResponse.SC_UNAUTHORIZED));
}
}
Its implementation outside for non-test:
@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true)
@Profile("!test")
public class ResourceServerConfiguration extends BaseResourceServerConfiguration {
@Value("${auth-server.url}")
private String authEndpoint;
@Value("${security.oauth2.client.client-id}")
private String clientId;
@Value("${security.oauth2.client.client-secret}")
private String clientSecret;
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.resourceId("ms/legacy");
}
@Bean
public ResourceServerTokenServices tokenService() {
RemoteTokenServices tokenServices = new RemoteTokenServices();
tokenServices.setClientId(clientId);
tokenServices.setClientSecret(clientSecret);
tokenServices.setCheckTokenEndpointUrl(authEndpoint + "/uaa/oauth/check_token");
return tokenServices;
}
}
And for tests:
@Configuration
@EnableResourceServer
@ActiveProfiles("test")
public class TestResourceServerConfigurerAdapter extends BaseResourceServerConfiguration {
@Override
public void configure(ResourceServerSecurityConfigurer security) throws Exception {
super.configure(security);
// Using OAuth with distant authorization service, stateless implies that the request tokens
// are verified each time against this service. In test, we don't want that because we need
// properly isolated tests. Setting this implies that the security is checked only locally
// and allows us to mock it with @WithMockUser, @AutoConfigureMockMvc and autowired MockMVC
security.stateless(false);
}
}
Inject token specific info with a request filter for tests:
@Component
@ActiveProfiles("test")
public class TestRequestFilter extends OncePerRequestFilter {
private Optional<InfoConf> nextInfoConf = Optional.empty();
// Request info is our request-scoped bean that holds JWT info
@Autowired
private RequestInfo info;
@Override
protected void doFilterInternal(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
if (nextInfoConf.isPresent()) {
info.setInfoConf(nextInfoConf.get());
}
filterChain.doFilter(httpServletRequest, httpServletResponse);
}
public void setNextInfoConf(InfoConf nextInfoConf) {
this.nextInfoConf = Optional.of(nextInfoConf);
}
public void clearNextInfoConf() {
nextInfoConf = Optional.empty();
}
}
And of course make the JWT parsing do nothing when there's no JWT.
We also wrote a small utility component to create the relevant info to inject.
A typical integration test will be like this:
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureMockMvc
@ActiveProfiles("test")
public class TestClass {
@Autowired
protected MockMvc mockMvc;
@Before
public void before() {
// Create an user in DB
// Inject the related information in our filter
}
@After
public void after() {
// Cleanup both in DB and filter
}
@Test
@WithMockUser
public void testThing() throws Exception {
// Use MockMVC
}
}
Another solution is to indeed mock the ResourceServerTokenServices
but in fact it's much more a pain to build proper tokens, and using Spring's standard security mock seems much more appropriate.
Upvotes: 1