スプリングコントローラーの単体テストを記述したいと思います。エンドポイントを保護するために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
を簡単にモックするものはありますか?
私は次のものを追加した後にテストすることができました:
いくつかのフィールドを追加します。
_@Autowired
private WebApplicationContext context:
private MockMvc mockMvc;
_
私の_@Before
_ setup()
メソッドで:
_mockMvc = MockMvcBuilders.webAppContextSetup(context)
.alwaysDo(print())
.apply(springSecurity())
.build();
_
私のテスト方法の内側:
_SecurityContext context = SecurityContextHolder.getContext();
Authentication authentication = context.getAuthentication();
_
キークロークトークンからクレームを読み取るメソッドにauthentication
オブジェクトを渡すと、問題なくテストを実行できます。
追伸_@WithMockUser
_アノテーションを忘れないでください
_@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")));
}
}
_