Mocking a Keycloak token for testing a Spring controller

穿精又带淫゛_ 提交于 2020-06-24 11:53:05

问题


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?


回答1:


I was able to test after adding the following things:

  1. Add some fields:

    @Autowired
    
        private WebApplicationContext context:
        private MockMvc mockMvc;
    
  2. In my @Before setup() method:

    mockMvc = MockMvcBuilders.webAppContextSetup(context)
       .alwaysDo(print())
       .apply(springSecurity())
       .build(); 
    
  3. Inside my test method:

    SecurityContext context = SecurityContextHolder.getContext();
    Authentication authentication = context.getAuthentication();
    

When I pass the authentication object to the method where I read the claims from the keycloak token I can run the test without any problems.

P.S. Don't forget the @WithMockUser annotation




回答2:


@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>
    <groupId>com.c4-soft.springaddons</groupId>
    <artifactId>spring-security-oauth2-test-webmvc-addons</artifactId>
    <version>2.0.3</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
    @WithMockKeycloackAuth(name = "ch4mpy", roles = "TESTER")
    public void whenGreetIsReachedWithValidSecurityContextThenUserIsActuallyGreeted() throws Exception {
        when(messageService.greet(any())).thenAnswer(invocation -> {
            final var auth = (Authentication) invocation.getArgument(0);
            return String.format("Hello %s! You are granted with %s.", auth.getName(),
                    auth.getAuthorities().stream().map(GrantedAuthority::getAuthority).collect(Collectors.toList()));
        });

        mockMvc().get("/greet").andExpect(content().string(is("Hello ch4mpy! You are granted with [ROLE_TESTER].")));
    }
}

Or "flow" API (MockMvc 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")));
    }

}


来源:https://stackoverflow.com/questions/49144953/mocking-a-keycloak-token-for-testing-a-spring-controller

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!