I’m writing an SPI which extends an access token with custom attributes. The reason for this is the limitation to 255 characters in the provided custom attributes. To achieve this i call a REST endpoint of the application which maintains the custom attributes. The application use the keyloak instance where my SPI lives for authorization. I have the restriction that all uri’s in the REST interface of the application have to be authorized.So i call the standard authorization uri of keycloak inside my SPI to obtain an access token. Neither using client credential nor password flow i always get a 401 response when i call the REST endpoint of my application.
I have already read the thread http://keycloak.discourse.group/t/create-accesstoken-inside-custom-spi/248/4, but this didn’t help in my case.
I’m using Keycloak 16.1.1 in a local Docker image for development. I used https://github.com/tholst/keycloak-json-graphql-remote-claim as blueprint for my code.
Additional Note: When i look at the Keycloak log, a javax.resource.RecourceExeption
with the text
IJ000453: Unable to get managed connection for java:jboss/datasources/KeycloakDS
occurs when my SPI try to obtain the token.
My code to obtain the token:
private String getAccessToken() throws IOException, InterruptedException {
String url = "http://localhost:9080/auth/realms/MyRealm/protocol/openid-connect/token";
Map<String, String> parameters = new HashMap<>();
parameters.put("grant_type", "client_credentials");
parameters.put("client_id", "My client id");
parameters.put("client_secret", "My client secret");
String form = parameters.keySet().stream()
.map(key -> key + "=" + URLEncoder.encode(parameters.get(key), StandardCharsets.UTF_8))
.collect(Collectors.joining("&"));
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder().uri(URI.create(url))
.headers("Content-Type", "application/x-www-form-urlencoded")
.POST(HttpRequest.BodyPublishers.ofString(form)).build();
HttpResponse<?> response = client.send(request, HttpResponse.BodyHandlers.ofString());
String body = response.body().toString();
var mapper = new ObjectMapper();
var root = mapper.readTree(body);
var tokenNode = root.findValue("access_token");
var token = tokenNode.asText();
return token;
}
My code to obtain the custom attributes:
public Map<String, Object> getUserAttributes(String userName) {
var uri = getEnsEndpointUri();
var timeout = getEnsConnectionTimeout();
try {
var accessToken = getAccessToken();
HttpRequest request = HttpRequest.newBuilder()
.uri(new URI(uri + userName))
.setHeader("Authorization", "Bearer " + token)
.GET()
.timeout(Duration.ofSeconds(timeout))
.build();
HttpClient client = HttpClient.newHttpClient();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() == HTTP_STATUS_OK) {
return retrieveToken(response.body());
}
} catch (URISyntaxException | IOException | InterruptedException e) {
e.printStackTrace();
if (e.getClass().equals(InterruptedException.class)) {
// Restore interrupted state...
Thread.currentThread().interrupt();
}
}
return new HashMap<>();
}