-2

I am trying to write a few tests for my Spring controller. The endpoints are secured with Keycloak (open id connect).

I tried mocking an authenticated user using the @WithMockUser annotation but I need to retrieve claims from the token (preferred_username) and I end up getting a null pointer exception from here:

return Long.parseLong(((KeycloakPrincipal) authentication.getPrincipal()).getKeycloakSecurityContext().getToken().getPreferredUsername());

Is there any way to mock the Keycloak token? I came across this similar question but I do not want to use the suggested external library.

Thank you guys in advance, any help would be greatly appreciated as I have been stuck on this problem for a while.

  • Already asked (and answered): https://stackoverflow.com/questions/62199105/how-to-unit-test-a-springboot-controller-secured-by-keycloak?rq=1 – ch4mp Jul 27 '22 at 18:50

1 Answers1

1

I came across this similar question but I do not want to use the suggested external library.

Well, you'd better reconsider that.

Are you using the deprecated Keycloak adapters?

If yes, and if you still don't want to use spring-addons-keycloak, you'll have to manualy populate test security context with a KeycloakAuthenticationToken instance or mock:

    @Test
    public void test() {
        final var principal = mock(Principal.class);
        when(principal.getName()).thenReturn("user");

        final var account = mock(OidcKeycloakAccount.class);
        when(account.getRoles()).thenReturn(Set.of("offline_access", "uma_authorization"));
        when(account.getPrincipal()).thenReturn(principal);

        final var authentication = mock(KeycloakAuthenticationToken.class);
        when(authentication.getAccount()).thenReturn(account);

        // post(...).with(authentication(authentication))
        // limits to testing secured @Controller with MockMvc
        // I prefer to set security context directly instead:
        SecurityContextHolder.getContext().setAuthentication(authentication);

        //TODO: invoque mockmvc to test @Controller or test any other type of @Component as usual
    }

You'll soon understand why this @WithMockKeycloakAuth was created.

If you already migrated to something else than Keycloak adapters, solution with manualy setting test-security context still applies, just adapt the Authentication instance. If your authentication type is JwtAuthenticationToken, you can use either:

    @Test
    void testWithPostProcessor() throws Exception {
        mockMvc.perform(get("/greet").with(jwt().jwt(jwt -> {
            jwt.claim("preferred_username", "Tonton Pirate");
        }).authorities(List.of(new SimpleGrantedAuthority("NICE_GUY"), new SimpleGrantedAuthority("AUTHOR")))))
                .andExpect(status().isOk())
                .andExpect(content().string("Hi Tonton Pirate! You are granted with: [NICE_GUY, AUTHOR]."));
    }
    @Test
    @WithMockJwtAuth(authorities = { "NICE_GUY", "AUTHOR" }, claims = @OpenIdClaims(preferredUsername = "Tonton Pirate"))
    void testWithPostProcessor() throws Exception {
        mockMvc.perform(get("/greet"))
                .andExpect(status().isOk())
                .andExpect(content().string("Hi Tonton Pirate! You are granted with: [NICE_GUY, AUTHOR]."));
    }

Note that only second option will work if you want to unit-test a secured @Component that is not a @Controller (a @Service or @Repository for instance).

My two cent advices:

  • drop Keycloak adapters now: it will disapear soon, is not adapted to boot 2.7+ (web-security config should not extend WebSecurityConfigurerAdapter any more) and is way too adherent to Keycloak. Just have a look at this tutorial to see how easy it can be to configure and unit-test a JWT resource-server (with identities issued by Keycloak or any other OIDC authorization-server)
  • if your team does not let you abandon Keycloak adapters yet, use @WithMockKeycloakAuth, you'll save tones of time and your test code will be way more readable.
ch4mp
  • 6,622
  • 6
  • 29
  • 49
  • Thank you, I ended up going with manually populating the security context. I personally would have used your library but sadly it is not entirely up to me. Also thank you for the extra advice. – horsemanwithnoname Jul 27 '22 at 19:29
  • You can present the argument this lib has the same author as JWT unit-testing support in Spring's `spring-security-test` (jwt() MockMvc post-processor and WebTestClient mutator). Plus, this are libs imported with `test` scope => note there at runtime... – ch4mp Jul 27 '22 at 19:42