Springboot testing with keycloak
Solution 1:
I just wrote a set of libs to ease unit-testing of secured Spring apps.
It includes a @WithMockKeycloackAuth
annotation, along with Keycloak dedicated MockMvc
request post-processor and WebTestClient
configurer / mutator
Sample usage:
@RunWith(SpringRunner.class)
@WebMvcTest(GreetingController.class)
@ContextConfiguration(classes = GreetingApp.class)
@ComponentScan(basePackageClasses = { KeycloakSecurityComponents.class, KeycloakSpringBootConfigResolver.class })
public class GreetingControllerTests extends ServletUnitTestingSupport {
@MockBean
MessageService messageService;
@Test
@WithMockKeycloackAuth
public void whenUserIsNotGrantedWithAuthorizedPersonelThenSecretRouteIsNotAccessible() throws Exception {
mockMvc().get("/secured-route").andExpect(status().isForbidden());
}
@Test
@WithMockKeycloackAuth("AUTHORIZED_PERSONNEL")
public void whenUserIsGrantedWithAuthorizedPersonelThenSecretRouteIsAccessible() throws Exception {
mockMvc().get("/secured-route").andExpect(content().string(is("secret route")));
}
@Test
@WithMockKeycloakAuth(
authorities = { "USER", "AUTHORIZED_PERSONNEL" },
id = @IdTokenClaims(sub = "42"),
oidc = @OidcStandardClaims(
email = "[email protected]",
emailVerified = true,
nickName = "Tonton-Pirate",
preferredUsername = "ch4mpy"),
privateClaims = @ClaimSet(stringClaims = @StringClaim(name = "foo", value = "bar")))
public void whenAuthenticatedWithKeycloakAuthenticationTokenThenCanGreet() throws Exception {
mockMvc().get("/greet")
.andExpect(status().isOk())
.andExpect(content().string(startsWith("Hello ch4mpy! You are granted with ")))
.andExpect(content().string(containsString("AUTHORIZED_PERSONNEL")))
.andExpect(content().string(containsString("USER")));
Depending of how much of the tooling I propose you wish to you might get spring-security-oauth2-test-addons
or spring-security-oauth2-test-webmvc-addons
from maven-central:
<dependency>
<groupId>com.c4-soft.springaddons</groupId>
<artifactId>spring-security-oauth2-test-addons</artifactId>
<version>2.3.4</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.c4-soft.springaddons</groupId>
<artifactId>spring-security-oauth2-test-webmvc-addons</artifactId>
<version>2.3.4</version>
<scope>test</scope>
</dependency>
First is enough if you're only interested in @WithMockKeycloakAuth
annotation. Second adds fluent API (MockMvc request post-processor) and other stuff like MockMvc wrapper with default values for content-type & accept headers
Solution 2:
I also found a way to do it, but it's quite ugly way. You can just turn off keycloak for testing purposes. Can be better?
in properties file(my is app-dev.properties) set:
keycloak.enabled = false
In application security config class I set
@ConditionalOnProperty(value = "keycloak.enabled", matchIfMissing = true)
public class KeycloakConfiguration extends KeycloakWebSecurityConfigurerAdapter {
I also created separate class with security config but only for testing purposes with these annotations
@Profile("app-dev.properties")
@Configuration
@EnableWebSecurity
public class TestSecConfig extends WebSecurityConfigurerAdapter{}
In integration test of controller
@ActiveProfiles("app-dev.properties")
@WebMvcTest(value = FunController.class)
@Import(TestSecConfig.class)
@TestPropertySource("classpath:app-dev.properties")
class FunControllerIT{}
source:
workaround https://github.com/spring-projects/spring-boot/issues/6514
Solution 3:
You could also annotate your Testclass with @AutoConfigureMockMvc(addFilters = false)
to disable filters from the application context.
Solution 4:
Solution for the Keycloak + Spring Security setup during testing is tricky but IMHO the following is only proper solution to test properly set environment. First thighs first we do not want to use security config conditionally as we want to test it also (RolesAllowed, Post and Pre annotations for example). We do not want to create special configuration for test also for the same reason. The way out is the following configuration:
@Configuration #mandatory
@EnableWebSecurity #mandatory
@EnableGlobalMethodSecurity(jsr250Enabled = true) #conditional
@EnableConfigurationProperties(KeycloakSpringBootProperties.class) #mandatory
@Slf4j #conditional
class WebSecurityConfig extends KeycloakWebSecurityConfigurerAdapter {
@Override
protected void configure(@NotNull HttpSecurity http) throws Exception {
super.configure(http);
...
}
@Bean
public @NotNull KeycloakConfigResolver keycloakConfigResolver() {
return new KeycloakSpringBootConfigResolver();
}
}
The really important is the presence of @EnableConfigurationProperties(KeycloakSpringBootProperties.class)
. Without it you will get NPE during tests. To you application.yml in the test resources or application-test.yml (the same goes analogically for the properties configuration) add the following:
keycloak:
enabled: false #Keycloak is not needed in full functionality
realm: mock #There is no configuration mock for Keycloak in case of testing. Realm must be set but it is not used
resource: mock #There is no configuration mock for Keycloak in case of testing. Resource must be set but it is not used
auth-server-url: http://mock #There is no configuration mock for Keycloak in case of testing. URL must be set but it is not used
bearer-only: true # Because Keycloak do redirect in case of unauthenticated user which leads to 302 status, we switch to strict Bearer mode
credentials:
secret: mock
With this setting and @WithMockUser
annotation your @WebMvcTest
will be running in the same security config as production with not errors.