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 info Is 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.
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 configuration security.stateless(false);
) test
profile Here 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.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.