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:
jwt()
request post processor for MockMvc I wrote (it is available fromspring-security-test
)
@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]."));
}
@WithMockJwtAuth
, same author, different lib
@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.