Categories
Coding

Programmatic Keycloak Configuration for Quarkus Integration Tests


Programmatic Keycloak Configuration for Quarkus Integration Tests

Quarkus offers an incredible testing framework, and its devservices feature is fantastic for getting applications up and running quickly. However, when your testing scenarios demand more intricate setups—like custom Keycloak realms and dynamic client configurations—you may find that a more hands-on approach is beneficial.

While Quarkus provides powerful defaults, advanced use cases sometimes require more granular control. For instance, you might encounter challenges when trying to override the default realm or find that properties like quarkus.keycloak.devservices.realm-path don’t behave as expected in all situations.

To gain full control and ensure a predictable test environment, let’s walk through how to build a custom test setup using your own QuarkusTestResourceLifecycleManager. 🔧


The Strategy: A Custom Test Resource

The core of this solution is to create our own implementation of QuarkusTestResourceLifecycleManager. This gives us complete authority over starting, configuring, and stopping a Keycloak instance for our tests.

Step 1: Add the Test Dependency

First, ensure your pom.xml includes the quarkus-test-keycloak-server dependency. This pulls in the necessary classes like KeycloakContainer and KeycloakTestClient.

<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-test-keycloak-server</artifactId>
    <scope>test</scope>
</dependency>

Step 2: Configure the Surefire Plugin

To make the setup more flexible, you can use the maven-surefire-plugin to pass system properties to your tests, such as specifying a particular Keycloak Docker image.

<plugin>
    <artifactId>maven-surefire-plugin</artifactId>
    <version>${maven-surefire-plugin.version}</version>
    <configuration>
        <systemPropertyVariables>
            <java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
            <maven.home>${maven.home}</maven.home>
            <keycloak.docker.image>quay.io/keycloak/keycloak:24.0.4</keycloak.docker.image>
        </systemPropertyVariables>
    </configuration>
</plugin>

Step 3: Implement the Custom Test Resource

This is where you take full control. The custom resource starts a KeycloakContainer and then uses KeycloakTestClient to programmatically configure it. A key advantage here is the ability to load a realm from a JSON file and modify it dynamically—in this case, setting the client secret—before the realm is created.

public class MyKeycloakTestResource implements QuarkusTestResourceLifecycleManager {

    private KeycloakContainer keycloak;

    @Override
    public Map<String, String> start() {
        keycloak = new KeycloakContainer()
                .withUseHttps(false);
        keycloak.start();

        final String path = MyResourceTest.class.getResource("/my-realm.json").getPath();
        KeycloakTestClient keycloakClient = new KeycloakTestClient(keycloak.getAuthServerUrl());

        // Load realm representation from a file
        final RealmRepresentation realmRepresentation = keycloakClient.readRealmFile(path);
        
        // Dynamically modify the client secret before creation
        realmRepresentation.getClients().stream().filter(c -> c.getClientId().equals("my-service"))
                .findFirst().get().setSecret("secret");

        keycloakClient.createRealm(realmRepresentation);

        // Return the configuration properties to be injected into the application
        Map<String, String> conf = new HashMap<>();
        conf.put("quarkus.oidc.auth-server-url", String.format("%s/realms/%s", keycloak.getAuthServerUrl(), "my-realm-name"));
        conf.put("quarkus.oidc.credentials.secret", "secret");
        conf.put("quarkus.oidc.client-id", "my-service");

        return conf;
    }

    @Override
    public void stop() {
        if (keycloak != null) {
            keycloak.stop();
        }
    }
}

Note: make sure to have the realm configuration file (i.e.: my-realm.json in the class path, such as in src/test/resources).


Step 4: Write and Annotate the Test

Finally, annotate your test class with @QuarkusTestResource to activate your custom manager. Your test can then use KeycloakTestClient to fetch a valid token for the service and user you configured in your realm file.

@QuarkusTest
@QuarkusTestResource(MyKeycloakTestResource.class)
public class MyResourceTest {

    private String getAccessToken() {
        // NOTE: The server URL for KeycloakTestClient is automatically
        // configured by the Quarkus test framework when using test resources.
        KeycloakTestClient keycloakClient = new KeycloakTestClient();
        return keycloakClient.getAccessToken("my-test-user", "password", "my-service");
    }

    @Test
    public void testPingServiceSuccessfully() {
        final String accessToken = getAccessToken();
        Assertions.assertNotNull(accessToken);

        given()
                .header("Content-Type", MediaType.APPLICATION_JSON)
                .header("Authorization", "Bearer " + accessToken)
                .when().post("/api/v1/my/restricted/api/ping")
                .then()
                .statusCode(Response.Status.OK.getStatusCode());
    }
}

Conclusion

While Quarkus defaults provide an excellent on-ramp, this custom QuarkusTestResourceLifecycleManager approach unlocks the power and flexibility needed for complex integration tests. It gives you predictable, fine-grained control over your security test infrastructure, enabling you to validate sophisticated configurations with confidence.

By Otavio Piske

Just another nerd

Leave a Reply

Your email address will not be published. Required fields are marked *