Keycloak Authentisierung in Spring Boot Tests

In der Software Entwicklung ist es immer wieder schön, wenn zwei Frameworks gut zusammenarbeiten. Ärgerlich ist es dann aber, wenn man dies nicht adäquat testen kann. Eines dieser unglückseligen Paare sind Keycloak und Spring Security.

Ein Spring REST Controller kann in einem JUnit Test auf seine korrekte Arbeitsweise geprüft werden. Neben der Validierung der Eingangsparameter und Generierung der korrekten REST Antwort, kann auch die Authentifizierung und Autorisierung überprüft werden.

@Test @WithMockBoardMember 
void findAll(@Count(10) List<MemberDto> memberDtos) throws Exception {
  when(service.findAll(any())).thenAnswer(a -> new PageImpl<>(memberDtos, a.getArgument(0), 42));

  mockMvc.perform(get("/api/v1/members"))
    .andExpect(status().isOk())
     andExpect(content().json("{"_embedded":{"members":[]}}"));
}

Der hier dargestellte JUnit 5 Test überprüft, ob der /api/v1/members Endpoint die gewünschte Antwort mit dem HTTP Status Code 200 generiert, wenn sie von einem Mitglied des Vereinsvorstandes aufgerufen wird. Der Spring Security Umgebung wird innerhalb des Tests mit der Annotation @WithMockUser vorgegaukelt, dass ein Benutzer mit der Rolle BOARD_MEMBER den REST Endpoint verwendet. Der Test ist aus zur besseren Lesbarkeit nicht direkt mit der Annotation @WithMockUser(roles = "BOARD_MEMBER") versehen, sondern indirekt über eine eigene Annotation @WithMockBoardMember.

@Retention(RetentionPolicy.RUNTIME)
@WithMockUser(roles = { "BOARD_MEMBER", "MEMBER")
public @interface WithMockBoardMember {
}

Diese Annotation verwendet die Annotation @WithMockUser(roles = { "BOARD_MEMBER", "MEMBER"}) und kann überall dort verwendet werden, wo die Authentisierung und Autorisierung von Mitglied des Vereinsvorstandes geprüft werden muss. Neben dem besseren Namen und der verkürzten Schreibweise, ist auch die Änderung der Rollen für Mitglieder des Vereinsvorstandes an einer einzelnen Stelle in den Tests gebündelt.

Leider funktioniert der Test nicht mehr, wenn die Sicherheit der Anwendung mit Keycloak sichergestellt werden soll. Die Mock-Implementierung für die Spring Security Tests beißt sich mit der Keycloak Implementierung.

Glücklicherweise existiert das Projekt spring-security-test addons, mit dem sich diese Inkompatibilität umgehen lässt. Dazu ist die zu erst einmal die folgende Dependencies in das eigene Projekt einzufügen.

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

Statt des in Spring Security Tests üblicherweise genutzten Annotation, existiert nun zusätzlich die Annotation @WithMockKeycloakAuth, die noch weitere Möglichkeiten von Keycloak unterstützt. Damit der obige Test mit Keycloak arbeitet, muss nun die eigene Annotation @WithMockBoardMember angepasst werden.

@Retention(RetentionPolicy.RUNTIME)
@WithMockKeycloakAuth(authorities = { "BOARD_MEMBER", "MEMBER")
public @interface WithMockBoardMember {

}