22

I want to write unit tests for my spring controller. I'm using keycloak's openid flow to secure my endpoints.

In my tests I'm using the @WithMockUser annotation to mock an authenticated user. My problem is that I'm reading the userId from the token of the principal. My unit test now fails because the userId I read from the token is null;

        if (principal instanceof KeycloakAuthenticationToken) {
            KeycloakAuthenticationToken authenticationToken = (KeycloakAuthenticationToken) principal;
            SimpleKeycloakAccount account = (SimpleKeycloakAccount) authenticationToken.getDetails();
            RefreshableKeycloakSecurityContext keycloakSecurityContext = account.getKeycloakSecurityContext();
            AccessToken token = keycloakSecurityContext.getToken();
            Map<String, Object> otherClaims = token.getOtherClaims();
            userId = otherClaims.get("userId").toString();
        }

Is there anything to easily mock the KeycloakAuthenticationToken?

Peter Lustig
  • 1,585
  • 1
  • 18
  • 34
  • Are you able to find any solution? If yes than please share your solution. Can you please also share a sample integration test. I'm also not able to find a way to do integration testing with mocking key-cloak. Thanks – emkays Jun 12 '18 at 08:45
  • I am also starting IT tests on same tech stack but failing on key cloak mocking or at least embedding key cloak into my app and then utilizing that. can anybody help if he has success. – Mubasher Aug 30 '18 at 05:12

2 Answers2

18

@WithmockUser configures the security-context with a UsernamePasswordAuthenticationToken. This can be just fine for most use-cases but when your app relies on another Authentication implementation (like your code does), you have to build or mock an instance of the right type and put it in the test security-context: SecurityContextHolder.getContext().setAuthentication(authentication);

Of course, you'll soon want to automate this, building your own annotation or RequestPostProcessor

... or ...

take one "off the shelf", like in this lib of mine, which is available from maven-central:

<dependency>
    <!-- just enough for @WithMockKeycloackAuth -->
    <groupId>com.c4-soft.springaddons</groupId>
    <artifactId>spring-security-oauth2-test-addons</artifactId>
    <version>3.0.1</version>
    <scope>test</scope>
</dependency>
<dependency>
    <!-- required only for WebMvc "fluent" API -->
    <groupId>com.c4-soft.springaddons</groupId>
    <artifactId>spring-security-oauth2-test-webmvc-addons</artifactId>
    <version>3.0.1</version>
    <scope>test</scope>
</dependency>

You can use it either with @WithMockKeycloackAuth annotations:

@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("TESTER")
    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" },
            claims = @OpenIdClaims(
                    sub = "42",
                    email = "ch4mp@c4-soft.com",
                    emailVerified = true,
                    nickName = "Tonton-Pirate",
                    preferredUsername = "ch4mpy",
                    otherClaims = @Claims(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")));
    }

Or MockMvc fluent API (RequestPostProcessor):

@RunWith(SpringRunner.class)
@WebMvcTest(GreetingController.class)
@ContextConfiguration(classes = GreetingApp.class)
@ComponentScan(basePackageClasses = { KeycloakSecurityComponents.class, KeycloakSpringBootConfigResolver.class })
public class GreetingControllerTest extends ServletKeycloakAuthUnitTestingSupport {
    @MockBean
    MessageService messageService;

    @Test
    public void whenUserIsNotGrantedWithAuthorizedPersonelThenSecretMethodIsNotAccessible() throws Exception {
        mockMvc().with(authentication().roles("TESTER")).get("/secured-method").andExpect(status().isForbidden());
    }

    @Test
    public void whenUserIsGrantedWithAuthorizedPersonelThenSecretMethodIsAccessible() throws Exception {
        mockMvc().with(authentication().roles("AUTHORIZED_PERSONNEL")).get("/secured-method")
                .andExpect(content().string(is("secret method")));
    }

}
ch4mp
  • 6,622
  • 6
  • 29
  • 49
  • 2
    Not sure why this was downvoted. I tested this using version 2.0.0 with spring boot 2.2.6 and keycloak 9.0.0 and it works fine. – Wim Deblauwe Apr 06 '20 at 10:10
  • Thanks for feedback ;) – ch4mp Apr 06 '20 at 17:52
  • Works like a charm. Could not able to access refresh token but I will take a deep look into it. – Fırat Küçük Jun 09 '21 at 06:54
  • @ch4mp does this library support mocking client roles? I have endpoints that are protected with specific client roles, so I would need to mock user with that. Thanks – cvetan Jun 09 '21 at 07:28
  • My lib allows you to set any claim with any value. If you don't find the standard claim under `id` or `oidc`, you might have a look at Keycloak private ones under `accessToken` or set your own private claims under `otherClaims`. My guess is what you are looking for is demonstrated in tests: https://github.com/ch4mpy/spring-addons/blob/3f818b676eb59c29499a9b05d9c2adbf1dc5e3f4/spring-security-oauth2-test-webmvc-addons/src/test/java/com/c4_soft/springaddons/samples/webmvc/keycloak/web/GreetingControllerAnnotatedTest.java#L90 – ch4mp Jun 09 '21 at 22:26
  • Worked like a charm for spring boot 2.5.0 and Keycloak 14.0.0. Not sure why this isn't the correct answer. Many thanks! – teuber789 Jul 07 '21 at 17:24
  • @ch4mp is it possible to mock client roles with this library of yours? I am trying to do that for some time now, because on our application we use client roles. The other part of system which uses realm roles, works, however I can't manage to get it work with client roles, I get 403. I went through sample projects, played around with options I found, but with no luck. – cvetan Sep 05 '21 at 22:41
  • I need more info about your project. How is configured "client roles" mapper on your Keycloak server? `resource_access.${client_id}.roles`? Have you checked which claim is actually actually filled with "client roles" in a production Keycloak token? Are roles correctly parsed in production? How do you use my lib in your tests? – ch4mp Sep 06 '21 at 03:08
  • I deleted my answer and marked this as the correct answer. My answer was only suitable for really simple and limited use cases and it helped me when I was facing this issue more than 3 years ago. Using this library is obviously a much better way to create tests for more advanced use cases. – Peter Lustig Oct 13 '21 at 04:14
  • Working perfectly fine. `@WithMockKeycloackAuth` can be used as class-level annotation for the whole test class. – shubh gaikwad Jan 17 '22 at 14:53
0

I don't like to add extra dependency, a specially when it related only for test case scenario. Also adding dependency in some project is a big process with security checks and needs to be approved by many Managers, seniors etc. So this is my solution that allows to mock Keycloak security context without instance of keycloak and other extra dependecies. This is copied form my project so adjustments are required. Hope it help.

@Test
    void shouldFooOnProtectedEndpoint() throws Exception {
        //given
        AccessToken token = new AccessToken();
        
        // by username i was differentiate is it allowed
        token.setPreferredUsername(SUBMITTER_USERNAME);

        KeycloakSecurityContext keycloakSecurityContext = mock(KeycloakSecurityContext.class);
        given(keycloakSecurityContext.getToken()).willReturn(token);

        KeycloakPrincipal principal = mock(KeycloakPrincipal.class);
        given(principal.getKeycloakSecurityContext()).willReturn(keycloakSecurityContext);

        Authentication auth = mock(Authentication.class);
        given(auth.getPrincipal()).willReturn(principal);

        SecurityContextHolder.getContext().setAuthentication(auth);

        ... test logic
}
  • Adding a dependency with `test` scope shouldn't be that complicated (imported code is not there at runtime and as so cannot be armful). Plus, this answer is basically the one I gave before the "... or ...". Also the `KeycloakAuthenticationToken` was deprecated with the rest of the Spring adapter a year ago (this adapters are not compatible with spring-security 6) – ch4mp Feb 10 '23 at 22:16