Home > other >  Testing a Spring controller secured with Keycloak
Testing a Spring controller secured with Keycloak

Time:07-28

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.

CodePudding user response:

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.
  • Related