web-dev-qa-db-ja.com

Springコントローラーをテストするための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();
        }

KeycloakAuthenticationTokenを簡単にモックするものはありますか?

9
Peter Lustig

私は次のものを追加した後にテストすることができました:

  1. いくつかのフィールドを追加します。

    _@Autowired
    
        private WebApplicationContext context:
        private MockMvc mockMvc;
    _
  2. 私の_@Before_ setup()メソッドで:

    _mockMvc = MockMvcBuilders.webAppContextSetup(context)
       .alwaysDo(print())
       .apply(springSecurity())
       .build(); 
    _
  3. 私のテスト方法の内側:

    _SecurityContext context = SecurityContextHolder.getContext();
    Authentication authentication = context.getAuthentication();
    _

キークロークトークンからクレームを読み取るメソッドにauthenticationオブジェクトを渡すと、問題なくテストを実行できます。

追伸_@WithMockUser_アノテーションを忘れないでください

2
Peter Lustig

_@WithmockUser_は、UsernamePasswordAuthenticationTokenを使用してセキュリティコンテキストを構成します。これはほとんどのユースケースで問題ありませんが、アプリが別の認証実装に依存している場合(コードのように)、適切なタイプのインスタンスをビルドまたはモックして、テストのセキュリティコンテキストに配置する必要があります:SecurityContextHolder.getContext().setAuthentication(authentication);

もちろん、すぐにこれを自動化して、独自の注釈またはRequestPostProcessorを構築する必要があります

...または...

次のように、1つ「既製」を使用します 私のlib 。これはmaven-centralから入手できます。

_<dependency>
    <groupId>com.c4-soft.springaddons</groupId>
    <artifactId>spring-security-oauth2-test-webmvc-addons</artifactId>
    <version>2.0.3</version>
    <scope>test</test>
</dependency>
_

_@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
    @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].")));
    }
}
_

または「フロー」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")));
    }

}
_
1
ch4mp