KeycloakHealthIndicator.java
package com.taxonomy.security.keycloak;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.health.contributor.Health;
import org.springframework.boot.health.contributor.HealthIndicator;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Component;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
/**
* Health indicator that checks whether the Keycloak JWKS endpoint is reachable.
* Appears in {@code /actuator/health} when the {@code keycloak} profile is active.
*/
@Component
@Profile("keycloak")
public class KeycloakHealthIndicator implements HealthIndicator {
private static final Logger log = LoggerFactory.getLogger(KeycloakHealthIndicator.class);
@Value("${spring.security.oauth2.resourceserver.jwt.jwk-set-uri:http://localhost:8180/realms/taxonomy/protocol/openid-connect/certs}")
private String jwkSetUri;
private final HttpClient httpClient = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(5))
.build();
@Override
public Health health() {
try {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(jwkSetUri))
.timeout(Duration.ofSeconds(5))
.GET()
.build();
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() == 200) {
return Health.up()
.withDetail("jwksEndpoint", jwkSetUri)
.build();
} else {
return Health.down()
.withDetail("jwksEndpoint", jwkSetUri)
.withDetail("statusCode", response.statusCode())
.build();
}
} catch (Exception e) {
log.debug("Keycloak health check failed: {}", e.getMessage());
return Health.down()
.withDetail("jwksEndpoint", jwkSetUri)
.withDetail("error", e.getMessage())
.build();
}
}
}