模拟Keycloak令牌以测试Spring控制器

Pet*_*tig 7 junit spring spring-security mockito keycloak

我想为我的弹簧控制器编写单元测试.我正在使用keycloak的openid流来保护我的端点.

在我的测试中,我使用@WithMockUser注释来模拟经过身份验证的用户.我的问题是我正在从校长的令牌中读取userId.我的单元测试现在失败了,因为userId我从令牌读取的是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();
        }
Run Code Online (Sandbox Code Playgroud)

有什么可以轻易嘲笑的KeycloakAuthenticationToken吗?

ch4*_*4mp 11

@WithmockUser使用UsernamePasswordAuthenticationToken. 这对于大多数用例来说都很好,但是当您的应用程序依赖于另一个身份验证实现(就像您的代码那样)时,您必须构建或模拟正确类型的实例并将其放入测试安全上下文中:SecurityContextHolder.getContext().setAuthentication(authentication);

当然,您很快就会想要自动化,构建您自己的注释或 RequestPostProcessor

... 或者 ...

拿一个“现成的”,就像在我的这个库中一样,可以从 maven-central 获得:

<dependency>
    <groupId>com.c4-soft.springaddons</groupId>
    <artifactId>spring-security-oauth2-test-webmvc-addons</artifactId>
    <version>2.3.4</version>
    <scope>test</scope>
</dependency>
Run Code Online (Sandbox Code Playgroud)

您可以将它与@WithMockKeycloackAuth注释一起使用:

@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" },
            id = @IdTokenClaims(sub = "42"),
            oidc = @OidcStandardClaims(
                    email = "ch4mp@c4-soft.com",
                    emailVerified = true,
                    nickName = "Tonton-Pirate",
                    preferredUsername = "ch4mpy"),
            privateClaims = @ClaimSet(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")));
    }
Run Code Online (Sandbox Code Playgroud)

或者 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")));
    }

}
Run Code Online (Sandbox Code Playgroud)

  • 不知道为什么这被否决了。我使用带有 spring boot 2.2.6 和 keycloak 9.0.0 的版本 2.0.0 对此进行了测试,它工作正常。 (2认同)