I have a main app that exposes endpoint like this. I have a test like this. It simply uses TestRestTemplate, calls actuator endpoint and checks health:
@ExtendWith(SpringExtension.class)
@SpringBootTest(classes = HealthDataImporterApplication.class, webEnvironment = WebEnvironment.RANDOM_PORT)
@TestPropertySource(locations = "classpath:application-test.properties")
public class HealthDataImporterApplicationTest {
@Autowired
private TestRestTemplate testRestTemplate;
@LocalServerPort
private int serverPort;
@Test
public void health() {
assertThat("status", read(get("health"), "$.status").equals("UP"));
}
private String get(final String resource) {
return testRestTemplate.getForObject(String.format("http://localhost:%s/actuator/%s", serverPort, resource),
String.class);
}
}
I now added SecurityConfig
class to add authentication to the app. Here is SecurityConfig class:
@EnableWebSecurity
public class SecurityConfig {
private static final String ACTUATOR_URL = "/actuator/**";
private static final String SCOPE_PREFIX = "SCOPE_";
private static final String PERMISSION_SCOPE = "scope:scope";
@Value("${auth0.audience}")
private String audience;
@Value("${spring.security.oauth2.resourceserver.jwt.issuer-uri}")
private String issuer;
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
/*
This is where we configure the security required for our endpoints and setup our app to serve as
an OAuth2 Resource Server, using JWT validation.
*/
http
.csrf().disable()
.authorizeRequests()
.antMatchers(HttpMethod.GET, ACTUATOR_URL).permitAll()
.anyRequest().hasAuthority(SCOPE_PREFIX + PERMISSION_SCOPE)
.and().cors()
.and().oauth2ResourceServer().jwt();
return http.build();
}
@Bean
JwtDecoder jwtDecoder() {
/*
By default, Spring Security does not validate the "aud" claim of the token, to ensure that this token is
indeed intended for our app. Adding our own validator is easy to do:
*/
NimbusJwtDecoder jwtDecoder = JwtDecoders.fromOidcIssuerLocation(issuer);
jwtDecoder.setJwtValidator(new DelegatingOAuth2TokenValidator<>(JwtValidators.createDefaultWithIssuer(issuer),
new AudienceValidator(audience)));
return jwtDecoder;
}
}
Main app imports this SecurityConfig
class so authentication is there. However, it fails the existing test because it complains it is missing auth0.audience
and other value. It seems like it is trying to actually call auth0 url, which I don't want the test to. Otherwise, I have to put actual auth0 audience and domain which is sensitive data.
How can I disable spring security with TestRestTemplate? Is it worth to test this class at all?
As you are using spring-boot, do not override the JwtDecoder just to check audiences. Use spring.security.oauth2.resourceserver.jwt.audiences
property instead (this is a comma separated list of acceptable audience).
Rather than deactivating security, I prefer to use mocked identities and include access-control in test coverage, using MockMvc
(yes, even in @SpringBootTest
with many components wired).
Refer to this other answer for details on how to do that in unit-test and integration-test.
Because I find request post-processors not that readable and I happen to unit-test secured components which are not controllers (like @Service
or @Respository
with method-security like @PreAuthorize
, @PostFilter
, etc.), I created a lib with test annotations for OAuth2:
@SpringBootTest(webEnvironment = WebEnvironment.MOCK)
@AutoConfigureMockMvc
class ApplicationIntegrationTest {
@Autowired
MockMvc api;
// actuator
@Test
void givenRequestIsAnonymous_whenGetStatus_thenOk() throws Exception {
api.get("/actuator/health")
.andExpect(status().isOk())
.andExpect(jsonPath("$.status").value("UP"));
}
// secured resource
@Test
void givenRequestIsAnonymous_whenGetMachin_thenUnauthorized() throws Exception {
api.perform(get("/api/v1/machin"))
.andExpect(status().isUnauthorized());
}
@Test
@WithMockJwtAuth("SCOPE_openid", "SCOPE_scope:scope")
void givenUserIsGrantedWithExpectedAuthority_whenGetMachin_thenOk() throws Exception {
api.perform(get("/api/v1/machin"))
.andExpect(status().isOk());
}
@Test
@WithMockJwtAuth("SCOPE_openid")
void givenUserIsNotGrantedWithExpectedAuthority_whenGetMachin_thenForbidden() throws Exception {
api.perform(get("/api/v1/machin"))
.andExpect(status().isForbidden());
}
}
As comparison, the same sample with just spring-security-test
:
@SpringBootTest(webEnvironment = WebEnvironment.MOCK)
@AutoConfigureMockMvc
class ApplicationIntegrationTest {
@Autowired
MockMvc api;
// actuator
@Test
void givenRequestIsAnonymous_whenGetStatus_thenOk() throws Exception {
api.perform(get("/actuator/health/liveness"))
.andExpect(status().isOk());
}
// secured resource
@Test
void givenRequestIsAnonymous_whenGetMachin_thenUnauthorized() throws Exception {
api.perform(get("/api/v1/machin"))
.andExpect(status().isUnauthorized());
}
@Test
void givenUserIsGrantedWithExpectedAuthority_whenGetMachin_thenOk() throws Exception {
api.perform(get("/api/v1/machin")
.with(jwt().jwt(jwt -> jwt.authorities(List.of(
new SimpleGrantedAuthority("SCOPE_openid"),
new SimpleGrantedAuthority("SCOPE_scope:scope"))))))
.andExpect(status().isOk());
}
@Test
void givenUserIsNotGrantedWithExpectedAuthority_whenGetMachin_thenForbidden() throws Exception {
api.perform(get("/api/v1/machin")
.with(jwt().jwt(jwt -> jwt.authorities(List.of(
new SimpleGrantedAuthority("SCOPE_openid"))))))
.andExpect(status().isForbidden());
}
}