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.