Spring Boot – Keycloak 集成測試與 Testcontainers

Spring Security
Remote
0
10:24 AM · Nov 30 ,2025

1. 簡介

集成測試對於驗證應用程序是否正常工作至關重要。 此外,我們應該正確測試身份驗證,因為它是一個敏感部分

Testcontainers 允許我們在測試階段啓動 Docker 容器,以便我們能夠使用實際的技術堆棧運行我們的測試。

在本文中,我們將看到如何使用 Testcontainers 在實際 Keycloak 實例上設置集成測試

2. Setting up Spring Security with Keycloak

We’ll need to set up Spring Security, Keycloak configuration, and, finally, Testcontainers.

2.1. Setting up Spring Boot and Spring Security

Let’s start by setting up security, thanks to Spring Security. We’ll need the spring-boot-starter-security dependency. So, let’s add it to our pom:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

We’ll use the spring-boot parent pom. Hence we don’t need to specify the version of the libraries specified in its dependencies management.

Next, let’s create a simple controller to return a User:

@RestController
@RequestMapping("/users")
public class UserController {

    @GetMapping("me")
    public UserDto getMe() {
        return new UserDto(1L, "janedoe", "Doe", "Jane", "[email protected]");
    }
}

At this point, we have a secure controller that responds to requests on “/users/me”. When launching the application, Spring Security generates a password for the user ‘user’, visible in the application logs.

2.2. Configuring Keycloak

The easiest way to launch a local Keycloak is to use Docker. Hence, let’s run a Keycloak container with an admin account already configured:

docker run -p 8081:8080 -e KEYCLOAK_ADMIN=admin -e KEYCLOAK_ADMIN_PASSWORD=admin quay.io/keycloak/keycloak:17.0.1 start-dev

Let’s open a browser to the URL http://localhost:8081 to access the Keycloak console:

Keycloak login page

Next, let’s create our realm. We’ll call it baeldung:

Keycloak create realm

We need to add a client, which we’ll name baeldung-api:

Keycloak create client

Finally, let’s add a Jane Doe user using the Users menu:

Keycloak create user

Now that we’ve created our user, we must assign it a password. Let’s choose s3cr3t and uncheck the temporary button:

Keycloak update password

We’ve now set up our Keycloak realm with a baeldung-api client and a Jane Doe user.

We’ll next configure Spring to use Keycloak as the identity provider.

2.3. Putting Both Together

First, we’ll delegate the identification control to a Keycloak server. For this, we’ll use the spring-boot-starter-oauth2-resource-server library. It will allow us to validate a JWT token with the Keycloak server. Hence, let’s add it to our pom:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>

Let’s continue by configuring Spring Security to add the OAuth 2 resource server support:

@Configuration
@ConditionalOnProperty(name = "keycloak.enabled", havingValue = "true", matchIfMissing = true)
public class WebSecurityConfiguration {

    @Bean
    protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
        return new NullAuthenticatedSessionStrategy();
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {

        return http.csrf()
            .disable()
            .cors()
            .and()
            .authorizeHttpRequests(auth -> auth.anyRequest()
                .authenticated())
            .oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt)
            .build();
    }
}

We’re setting up a new filter chain that will apply to all incoming requests. It will validate the bound JWT token against our Keycloak server.

As we’re building a stateless application with bearer-only authentication, we’ll use the NullAuthenticatedSessionStrategy as a session strategy. Moreover, @ConditionalOnProperty allows us to disable the Keycloak configuration by setting the keycloak.enabled property to false.

Finally, let’s add the configuration needed to connect to our Keycloak in our application.properties file:

keycloak.enabled=true
spring.security.oauth2.resourceserver.jwt.issuer-uri=http://localhost:8180/auth/realms/baeldung-api

Our application is now secure and queries Keycloak on each request to validate the authentication.

3. 設置 Keycloak 的測試容器

3.1. 導出 Realm 配置

Keycloak 容器在沒有配置的情況下啓動。因此,我們必須在容器啓動時導入它作為一個 JSON 文件。讓我們從當前正在運行的實例中導出該文件:

2 Screenshot-from-2022-06-22-22-56-31-1

不幸的是,Keycloak 不會通過管理界面導出用户。我們可以登錄到容器並使用 kc.sh export 命令。對於我們的示例,手動編輯生成的 realm-export.json 文件並添加我們的 Jane Doe 更方便。讓我們在最終大括號之前添加此配置:

"users": [
  {
    "username": "janedoe",
    "email": "[email protected]",
    "firstName": "Jane",
    "lastName": "Doe",
    "enabled": true,
    "credentials": [
      {
        "type": "password",
        "value": "s3cr3t"
      }
    ],
    "clientRoles": {
      "account": [
        "view-profile",
        "manage-account"
      ]
    }
  }
]

讓我們將 realm-export.json 文件包含到我們的項目中的 src/test/resources/keycloak 文件夾中。我們將使用它在 Keycloak 容器啓動時。

3.2. 設置 Testcontainers

讓我們添加 testcontainers 依賴項,以及 testcontainers-keycloak,它允許我們啓動 Keycloak 容器:

<dependency>
    <groupId>com.github.dasniko</groupId>
    <artifactId>testcontainers-keycloak</artifactId>
    <version>2.1.2</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.testcontainers</groupId>
    <artifactId>testcontainers</artifactId>
    <version>1.16.3</version>
</dependency>

接下來,讓我們創建一個所有測試都派生的類。我們使用它來配置由 Testcontainers 啓動的 Keycloak 容器:

@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
public abstract class KeycloakTestContainers {

    static {
        keycloak = new KeycloakContainer().withRealmImportFile("keycloak/realm-export.json");
        keycloak.start();
    }
}

聲明並啓動容器靜態將確保它將在所有測試中實例化並啓動一次。withRealmImportFile 方法來指定 Realm 的配置以在啓動時導入">我們正在使用 withRealmImportFile 方法來指定 Realm 的配置以在啓動時導入

3.3. Spring Boot 測試配置

Keycloak 容器使用隨機端口。因此,我們需要覆蓋 spring.security.oauth2.resourceserver.jwt.issuer-uri 配置,定義在我們的 application.properties 文件中。為此,我們將使用方便的 @DynamicPropertySource 註解:

@DynamicPropertySource
static void registerResourceServerIssuerProperty(DynamicPropertyRegistry registry) {
    registry.add("spring.security.oauth2.resourceserver.jwt.issuer-uri", () -> keycloak.getAuthServerUrl() + "/realms/baeldung");
}

4. 創建集成測試

現在我們已經有了負責啓動 Keycloak 容器並配置 Spring 屬性的主要測試類,讓我們創建一個調用我們 User 控制器的集成測試。

4.1. 獲取訪問令牌

首先,讓我們為抽象類 IntegrationTest 添加一個用於請求令牌的方法,使用 Jane Doe 的憑據:

URI authorizationURI = new URIBuilder(keycloak.getAuthServerUrl() + "/realms/baeldung/protocol/openid-connect/token").build();
WebClient webclient = WebClient.builder().build();
MultiValueMap<String, String> formData = new LinkedMultiValueMap<>();
formData.put("grant_type", Collections.singletonList("password"));
formData.put("client_id", Collections.singletonList("baeldung-api"));
formData.put("username", Collections.singletonList("[email protected]"));
formData.put("password", Collections.singletonList("s3cr3t"));

String result = webclient.post()
  .uri(authorizationURI)
  .contentType(MediaType.APPLICATION_FORM_URLENCODED)
  .body(BodyInserters.fromFormData(formData))
  .retrieve()
  .bodyToMono(String.class)
  .block();

在這裏,我們使用 Webflux 的 WebClient 向包含獲取訪問令牌所需的不同參數的表單進行 POST 請求。

最後,我們將 解析 Keycloak 服務器響應以從中提取令牌

具體來説,我們生成一個經典身份驗證字符串,其中包含 Bearer 關鍵字,後跟令牌的內容,以便將其用於標題中:

JacksonJsonParser jsonParser = new JacksonJsonParser();
return "Bearer " + jsonParser.parseMap(result)
  .get("access_token")
  .toString();

4.2. 創建集成測試

讓我們快速設置與我們配置的 Keycloak 容器的集成測試。我們將使用 RestAssured 和 Hamcrest 用於我們的測試。讓我們添加 rest-assured 依賴項:

<dependency>
    <groupId>io.rest-assured</groupId>
    <artifactId>rest-assured</artifactId>
    <scope>test</scope>
</dependency>

現在我們可以使用我們的抽象 IntegrationTest 類創建測試:

@Test
void givenAuthenticatedUser_whenGetMe_shouldReturnMyInfo() {

    given().header("Authorization", getJaneDoeBearer())
      .when()
      .get("/users/me")
      .then()
      .body("username", equalTo("janedoe"))
      .body("lastname", equalTo("Doe"))
      .body("firstname", equalTo("Jane"))
      .body("email", equalTo("[email protected]"));
}

結果是,我們的訪問令牌,從 Keycloak 檢索,已添加到請求的 Authorization 標題中。

5. 結論

在本文中,我們設置了針對實際通過 Testcontainers 管理的 Keycloak 的集成測試。我們導入了一個 Realm 配置,以便每次啓動測試階段時都能擁有預配置的環境。

發佈 評論

Some HTML is okay.