2021-05-28 18:29:06 +02:00
|
|
|
package org.gcube.common.keycloak;
|
|
|
|
|
2022-05-19 19:40:09 +02:00
|
|
|
import static org.gcube.common.keycloak.model.OIDCConstants.AUDIENCE_PARAMETER;
|
|
|
|
import static org.gcube.common.keycloak.model.OIDCConstants.CLIENT_CREDENTIALS_GRANT_TYPE;
|
|
|
|
import static org.gcube.common.keycloak.model.OIDCConstants.CLIENT_ID_PARAMETER;
|
|
|
|
import static org.gcube.common.keycloak.model.OIDCConstants.CLIENT_SECRET_PARAMETER;
|
|
|
|
import static org.gcube.common.keycloak.model.OIDCConstants.GRANT_TYPE_PARAMETER;
|
|
|
|
import static org.gcube.common.keycloak.model.OIDCConstants.PERMISSION_PARAMETER;
|
|
|
|
import static org.gcube.common.keycloak.model.OIDCConstants.REFRESH_TOKEN_GRANT_TYPE;
|
|
|
|
import static org.gcube.common.keycloak.model.OIDCConstants.REFRESH_TOKEN_PARAMETER;
|
|
|
|
import static org.gcube.common.keycloak.model.OIDCConstants.TOKEN_PARAMETER;
|
|
|
|
import static org.gcube.common.keycloak.model.OIDCConstants.UMA_TOKEN_GRANT_TYPE;
|
2021-05-28 18:29:06 +02:00
|
|
|
import static org.gcube.resources.discovery.icclient.ICFactory.clientFor;
|
|
|
|
import static org.gcube.resources.discovery.icclient.ICFactory.queryFor;
|
|
|
|
|
|
|
|
import java.io.UnsupportedEncodingException;
|
|
|
|
import java.net.MalformedURLException;
|
|
|
|
import java.net.URL;
|
|
|
|
import java.net.URLEncoder;
|
|
|
|
import java.util.Arrays;
|
|
|
|
import java.util.Base64;
|
|
|
|
import java.util.Collections;
|
|
|
|
import java.util.HashMap;
|
|
|
|
import java.util.List;
|
|
|
|
import java.util.Map;
|
|
|
|
import java.util.stream.Collectors;
|
|
|
|
|
|
|
|
import org.gcube.common.gxrest.request.GXHTTPStringRequest;
|
|
|
|
import org.gcube.common.gxrest.response.inbound.GXInboundResponse;
|
2021-12-09 15:05:26 +01:00
|
|
|
import org.gcube.common.keycloak.model.ModelUtils;
|
2022-05-19 19:40:09 +02:00
|
|
|
import org.gcube.common.keycloak.model.TokenIntrospectionResponse;
|
2021-05-28 18:29:06 +02:00
|
|
|
import org.gcube.common.keycloak.model.TokenResponse;
|
|
|
|
import org.gcube.common.resources.gcore.ServiceEndpoint;
|
|
|
|
import org.gcube.common.resources.gcore.ServiceEndpoint.AccessPoint;
|
|
|
|
import org.gcube.common.scope.api.ScopeProvider;
|
|
|
|
import org.gcube.resources.discovery.client.api.DiscoveryClient;
|
|
|
|
import org.gcube.resources.discovery.client.queries.api.SimpleQuery;
|
|
|
|
|
|
|
|
public class DefaultKeycloakClient implements KeycloakClient {
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public URL findTokenEndpointURL() throws KeycloakClientException {
|
2021-06-25 10:52:41 +02:00
|
|
|
logger.debug("Checking ScopeProvider's scope presence and format");
|
|
|
|
String originalScope = ScopeProvider.instance.get();
|
|
|
|
if (originalScope == null || !originalScope.startsWith("/") || originalScope.length() < 2) {
|
|
|
|
throw new KeycloakClientException(originalScope == null ? "Scope not found in ScopeProvider"
|
|
|
|
: "Bad scope name found: " + originalScope);
|
|
|
|
}
|
|
|
|
logger.debug("Assuring use the rootVO to query the endpoint simple query. Actual scope is: {}", originalScope);
|
|
|
|
String rootVOScope = "/" + originalScope.split("/")[1];
|
|
|
|
logger.debug("Setting rootVO scope into provider as: {}", rootVOScope);
|
|
|
|
|
2022-03-30 12:01:42 +02:00
|
|
|
List<AccessPoint> accessPoints = null;
|
2021-05-28 18:29:06 +02:00
|
|
|
|
2022-03-30 12:01:42 +02:00
|
|
|
// trying to be thread safe at least for these calls
|
|
|
|
synchronized (ScopeProvider.instance) {
|
|
|
|
boolean scopeModified = false;
|
|
|
|
if (!ScopeProvider.instance.get().equals(rootVOScope)) {
|
|
|
|
logger.debug("Overriding scope in the provider with rootVO scope : {}", rootVOScope);
|
|
|
|
ScopeProvider.instance.set(rootVOScope);
|
|
|
|
scopeModified = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
logger.debug("Creating simple query");
|
|
|
|
SimpleQuery query = queryFor(ServiceEndpoint.class);
|
|
|
|
query.addCondition(
|
|
|
|
String.format("$resource/Profile/Category/text() eq '%s'", CATEGORY))
|
|
|
|
.addCondition(String.format("$resource/Profile/Name/text() eq '%s'", NAME))
|
|
|
|
.setResult(String.format("$resource/Profile/AccessPoint[Description/text() eq '%s']", DESCRIPTION));
|
2021-05-28 18:29:06 +02:00
|
|
|
|
2022-03-30 12:01:42 +02:00
|
|
|
logger.debug("Creating client for AccessPoint");
|
|
|
|
DiscoveryClient<AccessPoint> client = clientFor(AccessPoint.class);
|
2021-05-28 18:29:06 +02:00
|
|
|
|
2022-03-30 12:01:42 +02:00
|
|
|
logger.trace("Submitting query: {}", query);
|
|
|
|
accessPoints = client.submit(query);
|
|
|
|
|
|
|
|
if (scopeModified) {
|
|
|
|
logger.debug("Resetting scope into provider to the original value: {}", originalScope);
|
|
|
|
ScopeProvider.instance.set(originalScope);
|
|
|
|
}
|
|
|
|
}
|
2021-06-25 10:52:41 +02:00
|
|
|
|
2021-05-28 18:29:06 +02:00
|
|
|
if (accessPoints.size() == 0) {
|
|
|
|
throw new KeycloakClientException("Service endpoint not found");
|
|
|
|
} else if (accessPoints.size() > 1) {
|
|
|
|
throw new KeycloakClientException("Found more than one endpoint with query");
|
|
|
|
}
|
|
|
|
String address = accessPoints.iterator().next().address();
|
|
|
|
logger.debug("Found address: {}", address);
|
|
|
|
try {
|
|
|
|
return new URL(address);
|
|
|
|
} catch (MalformedURLException e) {
|
|
|
|
throw new KeycloakClientException("Cannot create URL from address: " + address, e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-05-19 19:40:09 +02:00
|
|
|
@Override
|
|
|
|
public URL computeIntrospectionEndpointURL() throws KeycloakClientException {
|
|
|
|
return computeIntrospectionEndpointURL(findTokenEndpointURL());
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public URL computeIntrospectionEndpointURL(URL tokenEndpointURL) throws KeycloakClientException {
|
|
|
|
logger.debug("Computing introspection URL starting from token endpoint URL: {}", tokenEndpointURL);
|
|
|
|
try {
|
|
|
|
URL introspectionURL = null;
|
|
|
|
if (tokenEndpointURL.getPath().endsWith("token/")) {
|
|
|
|
introspectionURL = new URL(tokenEndpointURL, "introspect");
|
|
|
|
} else {
|
|
|
|
introspectionURL = new URL(tokenEndpointURL, "token/introspect");
|
|
|
|
}
|
|
|
|
logger.debug("Computed introspection URL is: {}", introspectionURL);
|
|
|
|
return introspectionURL;
|
|
|
|
} catch (MalformedURLException e) {
|
|
|
|
throw new KeycloakClientException("Cannot create introspection URL from token URL: " + tokenEndpointURL, e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-30 12:01:42 +02:00
|
|
|
@Override
|
|
|
|
public TokenResponse queryOIDCToken(String clientId, String clientSecret) throws KeycloakClientException {
|
|
|
|
return queryOIDCToken(findTokenEndpointURL(), clientId, clientSecret);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public TokenResponse queryOIDCToken(URL tokenURL, String clientId, String clientSecret)
|
|
|
|
throws KeycloakClientException {
|
|
|
|
|
|
|
|
return queryOIDCToken(tokenURL, constructBasicAuthenticationHeader(clientId, clientSecret));
|
|
|
|
}
|
|
|
|
|
|
|
|
protected String constructBasicAuthenticationHeader(String clientId, String clientSecret) {
|
|
|
|
return "Basic " + Base64.getEncoder().encodeToString((clientId + ":" + clientSecret).getBytes());
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public TokenResponse queryOIDCToken(URL tokenURL, String authorization) throws KeycloakClientException {
|
|
|
|
logger.debug("Querying OIDC token from Keycloak server with URL: {}", tokenURL);
|
|
|
|
|
|
|
|
Map<String, List<String>> params = new HashMap<>();
|
|
|
|
params.put(GRANT_TYPE_PARAMETER, Arrays.asList(CLIENT_CREDENTIALS_GRANT_TYPE));
|
|
|
|
|
|
|
|
return performRequest(tokenURL, authorization, params);
|
|
|
|
}
|
|
|
|
|
2021-05-28 18:29:06 +02:00
|
|
|
@Override
|
|
|
|
public TokenResponse queryUMAToken(String clientId, String clientSecret, List<String> permissions)
|
|
|
|
throws KeycloakClientException {
|
|
|
|
|
|
|
|
return queryUMAToken(clientId, clientSecret, ScopeProvider.instance.get(), permissions);
|
|
|
|
}
|
2021-06-25 10:52:41 +02:00
|
|
|
|
2022-03-30 12:01:42 +02:00
|
|
|
@Override
|
|
|
|
public TokenResponse queryUMAToken(TokenResponse oidcTokenResponse, String audience, List<String> permissions)
|
|
|
|
throws KeycloakClientException {
|
|
|
|
|
|
|
|
return queryUMAToken(findTokenEndpointURL(), constructBeareAuthenticationHeader(oidcTokenResponse), audience,
|
|
|
|
permissions);
|
|
|
|
}
|
|
|
|
|
2021-05-28 18:29:06 +02:00
|
|
|
@Override
|
|
|
|
public TokenResponse queryUMAToken(String clientId, String clientSecret, String audience,
|
|
|
|
List<String> permissions) throws KeycloakClientException {
|
|
|
|
|
|
|
|
return queryUMAToken(findTokenEndpointURL(), clientId, clientSecret, audience, permissions);
|
|
|
|
}
|
|
|
|
|
2022-03-30 12:01:42 +02:00
|
|
|
@Override
|
|
|
|
public TokenResponse queryUMAToken(URL tokenURL, TokenResponse oidcTokenResponse, String audience,
|
|
|
|
List<String> permissions) throws KeycloakClientException {
|
|
|
|
|
|
|
|
return queryUMAToken(tokenURL, constructBeareAuthenticationHeader(oidcTokenResponse), audience, permissions);
|
|
|
|
}
|
|
|
|
|
|
|
|
protected String constructBeareAuthenticationHeader(TokenResponse oidcTokenResponse) {
|
|
|
|
return "Bearer " + oidcTokenResponse.getAccessToken();
|
|
|
|
}
|
|
|
|
|
2021-05-28 18:29:06 +02:00
|
|
|
@Override
|
|
|
|
public TokenResponse queryUMAToken(URL tokenURL, String clientId, String clientSecret, String audience,
|
|
|
|
List<String> permissions) throws KeycloakClientException {
|
|
|
|
|
|
|
|
return queryUMAToken(tokenURL,
|
2022-03-30 12:01:42 +02:00
|
|
|
constructBasicAuthenticationHeader(clientId, clientSecret),
|
2021-05-28 18:29:06 +02:00
|
|
|
audience, permissions);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public TokenResponse queryUMAToken(URL tokenURL, String authorization, String audience,
|
|
|
|
List<String> permissions) throws KeycloakClientException {
|
|
|
|
|
2022-03-30 12:01:42 +02:00
|
|
|
if (audience == null || "".equals(audience)) {
|
|
|
|
throw new KeycloakClientException("Audience must be not null nor empty");
|
2021-06-25 10:52:41 +02:00
|
|
|
}
|
|
|
|
|
2022-03-30 12:01:42 +02:00
|
|
|
logger.debug("Querying UMA token from Keycloak server with URL: {}", tokenURL);
|
|
|
|
|
|
|
|
Map<String, List<String>> params = new HashMap<>();
|
|
|
|
params.put(GRANT_TYPE_PARAMETER, Arrays.asList(UMA_TOKEN_GRANT_TYPE));
|
|
|
|
|
|
|
|
try {
|
|
|
|
params.put(AUDIENCE_PARAMETER, Arrays.asList(URLEncoder.encode(checkAudience(audience), "UTF-8")));
|
|
|
|
} catch (UnsupportedEncodingException e) {
|
|
|
|
logger.error("Can't URL encode audience: {}", audience, e);
|
2021-06-25 10:52:41 +02:00
|
|
|
}
|
|
|
|
|
2022-03-30 12:01:42 +02:00
|
|
|
if (permissions != null && !permissions.isEmpty()) {
|
|
|
|
params.put(
|
|
|
|
PERMISSION_PARAMETER, permissions.stream().map(s -> {
|
|
|
|
try {
|
|
|
|
return URLEncoder.encode(s, "UTF-8");
|
|
|
|
} catch (UnsupportedEncodingException e) {
|
|
|
|
return "";
|
|
|
|
}
|
|
|
|
}).collect(Collectors.toList()));
|
2021-06-25 10:52:41 +02:00
|
|
|
}
|
|
|
|
|
2022-03-30 12:01:42 +02:00
|
|
|
return performRequest(tokenURL, authorization, params);
|
|
|
|
}
|
|
|
|
|
|
|
|
protected TokenResponse performRequest(URL tokenURL, String authorization, Map<String, List<String>> params)
|
|
|
|
throws KeycloakClientException {
|
|
|
|
|
|
|
|
if (tokenURL == null) {
|
|
|
|
throw new KeycloakClientException("Token URL must be not null");
|
|
|
|
}
|
2021-05-28 18:29:06 +02:00
|
|
|
|
2022-03-30 12:01:42 +02:00
|
|
|
if (authorization == null || "".equals(authorization)) {
|
|
|
|
throw new KeycloakClientException("Authorization must be not null nor empty");
|
|
|
|
}
|
2021-05-28 18:29:06 +02:00
|
|
|
// Constructing request object
|
|
|
|
GXHTTPStringRequest request;
|
|
|
|
try {
|
|
|
|
String queryString = params.entrySet().stream()
|
|
|
|
.flatMap(p -> p.getValue().stream().map(v -> p.getKey() + "=" + v))
|
|
|
|
.reduce((p1, p2) -> p1 + "&" + p2).orElse("");
|
|
|
|
|
|
|
|
request = GXHTTPStringRequest.newRequest(tokenURL.toString())
|
|
|
|
.header("Content-Type", "application/x-www-form-urlencoded").withBody(queryString);
|
|
|
|
|
|
|
|
request.isExternalCall(true);
|
|
|
|
if (authorization != null) {
|
|
|
|
logger.debug("Adding authorization header as: {}", authorization);
|
|
|
|
request = request.header("Authorization", authorization);
|
|
|
|
}
|
|
|
|
} catch (Exception e) {
|
|
|
|
throw new KeycloakClientException("Cannot construct the request object correctly", e);
|
|
|
|
}
|
|
|
|
|
|
|
|
GXInboundResponse response;
|
|
|
|
try {
|
|
|
|
response = request.post();
|
|
|
|
} catch (Exception e) {
|
|
|
|
throw new KeycloakClientException("Cannot send request correctly", e);
|
|
|
|
}
|
|
|
|
if (response.isSuccessResponse()) {
|
|
|
|
try {
|
|
|
|
return response.tryConvertStreamedContentFromJson(TokenResponse.class);
|
|
|
|
} catch (Exception e) {
|
|
|
|
throw new KeycloakClientException("Cannot construct token response object correctly", e);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
throw KeycloakClientException.create("Unable to get token", response.getHTTPCode(),
|
|
|
|
response.getHeaderFields()
|
2021-12-09 15:05:26 +01:00
|
|
|
.getOrDefault("content-type", Collections.singletonList("unknown/unknown")).get(0),
|
2021-05-28 18:29:06 +02:00
|
|
|
response.getMessage());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private static String checkAudience(String audience) {
|
|
|
|
if (audience.startsWith("/")) {
|
|
|
|
try {
|
|
|
|
logger.trace("Audience was provided in non URL encoded form, encoding it");
|
|
|
|
return URLEncoder.encode(audience, "UTF-8");
|
|
|
|
} catch (UnsupportedEncodingException e) {
|
|
|
|
logger.error("Cannot URL encode 'audience'", e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return audience;
|
|
|
|
}
|
|
|
|
|
2021-12-09 15:05:26 +01:00
|
|
|
@Override
|
|
|
|
public TokenResponse refreshToken(TokenResponse tokenResponse) throws KeycloakClientException {
|
|
|
|
return refreshToken((String) null, tokenResponse);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public TokenResponse refreshToken(URL tokenURL, TokenResponse tokenResponse) throws KeycloakClientException {
|
|
|
|
return refreshToken(tokenURL, null, null, tokenResponse);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public TokenResponse refreshToken(String clientId, TokenResponse tokenResponse) throws KeycloakClientException {
|
|
|
|
return refreshToken(clientId, null, tokenResponse);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public TokenResponse refreshToken(String clientId, String clientSecret, TokenResponse tokenResponse)
|
|
|
|
throws KeycloakClientException {
|
|
|
|
|
|
|
|
return refreshToken(findTokenEndpointURL(), clientId, clientSecret, tokenResponse);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public TokenResponse refreshToken(URL tokenURL, String clientId, String clientSecret, TokenResponse tokenResponse)
|
|
|
|
throws KeycloakClientException {
|
|
|
|
|
|
|
|
if (clientId == null) {
|
|
|
|
logger.debug("Client id not set, trying to get it from access token info");
|
|
|
|
try {
|
|
|
|
clientId = ModelUtils.getClientIdFromToken(ModelUtils.getAccessTokenFrom(tokenResponse));
|
|
|
|
} catch (Exception e) {
|
|
|
|
throw new KeycloakClientException("Cannot construct access token object from token response", e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return refreshToken(tokenURL, clientId, clientSecret, tokenResponse.getRefreshToken());
|
|
|
|
}
|
|
|
|
|
2021-12-17 17:56:56 +01:00
|
|
|
@Override
|
|
|
|
public TokenResponse refreshToken(String refreshTokenJWTString) throws KeycloakClientException {
|
|
|
|
try {
|
|
|
|
String clientId = ModelUtils.getClientIdFromToken(ModelUtils.getRefreshTokenFrom(refreshTokenJWTString));
|
|
|
|
return refreshToken(clientId, refreshTokenJWTString);
|
|
|
|
} catch (Exception e) {
|
|
|
|
throw new KeycloakClientException("Cannot construct access token object from token response", e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-09 15:05:26 +01:00
|
|
|
@Override
|
|
|
|
public TokenResponse refreshToken(String clientId, String refreshTokenJWTString) throws KeycloakClientException {
|
|
|
|
return refreshToken(clientId, null, refreshTokenJWTString);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public TokenResponse refreshToken(String clientId, String clientSecret, String refreshTokenJWTString)
|
|
|
|
throws KeycloakClientException {
|
|
|
|
|
|
|
|
return refreshToken(findTokenEndpointURL(), clientId, clientSecret, refreshTokenJWTString);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public TokenResponse refreshToken(URL tokenURL, String clientId, String clientSecret, String refreshTokenJWTString)
|
|
|
|
throws KeycloakClientException {
|
|
|
|
|
|
|
|
if (tokenURL == null) {
|
|
|
|
throw new KeycloakClientException("Token URL must be not null");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (clientId == null || "".equals(clientId)) {
|
|
|
|
throw new KeycloakClientException("Client id must be not null nor empty");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (refreshTokenJWTString == null || "".equals(clientId)) {
|
|
|
|
throw new KeycloakClientException("Refresh token JWT encoded string must be not null nor empty");
|
|
|
|
}
|
|
|
|
|
|
|
|
logger.debug("Refreshing token from Keycloak server with URL: {}", tokenURL);
|
|
|
|
|
|
|
|
// Constructing request object
|
|
|
|
GXHTTPStringRequest request;
|
|
|
|
try {
|
|
|
|
Map<String, String> params = new HashMap<>();
|
|
|
|
params.put(GRANT_TYPE_PARAMETER, REFRESH_TOKEN_GRANT_TYPE);
|
|
|
|
params.put(REFRESH_TOKEN_PARAMETER, refreshTokenJWTString);
|
|
|
|
params.put(CLIENT_ID_PARAMETER, URLEncoder.encode(clientId, "UTF-8"));
|
|
|
|
if (clientSecret != null && !"".equals(clientSecret)) {
|
|
|
|
params.put(CLIENT_SECRET_PARAMETER, URLEncoder.encode(clientSecret, "UTF-8"));
|
|
|
|
}
|
2022-05-19 19:40:09 +02:00
|
|
|
|
2021-12-09 15:05:26 +01:00
|
|
|
String queryString = params.entrySet().stream()
|
|
|
|
.map(p -> p.getKey() + "=" + p.getValue())
|
|
|
|
.reduce((p1, p2) -> p1 + "&" + p2).orElse("");
|
|
|
|
|
|
|
|
request = GXHTTPStringRequest.newRequest(tokenURL.toString()).header("Content-Type",
|
|
|
|
"application/x-www-form-urlencoded").withBody(queryString);
|
|
|
|
|
|
|
|
request.isExternalCall(true);
|
|
|
|
} catch (Exception e) {
|
|
|
|
throw new KeycloakClientException("Cannot construct the request object correctly", e);
|
|
|
|
}
|
|
|
|
|
|
|
|
GXInboundResponse response;
|
|
|
|
try {
|
|
|
|
response = request.post();
|
|
|
|
} catch (Exception e) {
|
|
|
|
throw new KeycloakClientException("Cannot send request correctly", e);
|
|
|
|
}
|
|
|
|
if (response.isSuccessResponse()) {
|
|
|
|
try {
|
|
|
|
return response.tryConvertStreamedContentFromJson(TokenResponse.class);
|
|
|
|
} catch (Exception e) {
|
|
|
|
throw new KeycloakClientException("Cannot construct token response object correctly", e);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
throw KeycloakClientException.create("Unable to get token", response.getHTTPCode(),
|
|
|
|
response.getHeaderFields()
|
|
|
|
.getOrDefault("content-type", Collections.singletonList("unknown/unknown")).get(0),
|
|
|
|
response.getMessage());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-05-19 19:40:09 +02:00
|
|
|
@Override
|
|
|
|
public TokenIntrospectionResponse introspectAccessToken(String clientId, String clientSecret,
|
|
|
|
String accessTokenJWTString) throws KeycloakClientException {
|
|
|
|
|
|
|
|
return introspectAccessToken(computeIntrospectionEndpointURL(), clientId, clientSecret, accessTokenJWTString);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public TokenIntrospectionResponse introspectAccessToken(URL introspectionURL, String clientId, String clientSecret,
|
|
|
|
String accessTokenJWTString) throws KeycloakClientException {
|
|
|
|
|
|
|
|
if (introspectionURL == null) {
|
|
|
|
throw new KeycloakClientException("Introspection URL must be not null");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (clientId == null || "".equals(clientId)) {
|
|
|
|
throw new KeycloakClientException("Client id must be not null nor empty");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (clientSecret == null || "".equals(clientSecret)) {
|
|
|
|
throw new KeycloakClientException("Client secret must be not null nor empty");
|
|
|
|
}
|
|
|
|
|
|
|
|
logger.debug("Verifying access token against Keycloak server with URL: {}", introspectionURL);
|
|
|
|
|
|
|
|
// Constructing request object
|
|
|
|
GXHTTPStringRequest request;
|
|
|
|
try {
|
|
|
|
Map<String, String> params = new HashMap<>();
|
|
|
|
params.put(TOKEN_PARAMETER, accessTokenJWTString);
|
|
|
|
|
|
|
|
String queryString = params.entrySet().stream()
|
|
|
|
.map(p -> p.getKey() + "=" + p.getValue())
|
|
|
|
.reduce((p1, p2) -> p1 + "&" + p2).orElse("");
|
|
|
|
|
|
|
|
request = GXHTTPStringRequest.newRequest(introspectionURL.toString()).header("Content-Type",
|
|
|
|
"application/x-www-form-urlencoded").withBody(queryString);
|
|
|
|
|
|
|
|
request.isExternalCall(true);
|
|
|
|
request = request.header("Authorization", constructBasicAuthenticationHeader(clientId, clientSecret));
|
|
|
|
} catch (Exception e) {
|
|
|
|
throw new KeycloakClientException("Cannot construct the request object correctly", e);
|
|
|
|
}
|
|
|
|
|
|
|
|
GXInboundResponse response;
|
|
|
|
try {
|
|
|
|
response = request.post();
|
|
|
|
} catch (Exception e) {
|
|
|
|
throw new KeycloakClientException("Cannot send request correctly", e);
|
|
|
|
}
|
|
|
|
if (response.isSuccessResponse()) {
|
|
|
|
try {
|
|
|
|
return response.tryConvertStreamedContentFromJson(TokenIntrospectionResponse.class);
|
|
|
|
} catch (Exception e) {
|
|
|
|
throw new KeycloakClientException("Cannot construct introspection response object correctly", e);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
throw KeycloakClientException.create("Unable to get token introspection response", response.getHTTPCode(),
|
|
|
|
response.getHeaderFields()
|
|
|
|
.getOrDefault("content-type", Collections.singletonList("unknown/unknown")).get(0),
|
|
|
|
response.getMessage());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public boolean isAccessTokenVerified(String clientId, String clientSecret, String accessTokenJWTString)
|
|
|
|
throws KeycloakClientException {
|
|
|
|
|
|
|
|
return isAccessTokenVerified(computeIntrospectionEndpointURL(), clientId, clientSecret, accessTokenJWTString);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public boolean isAccessTokenVerified(URL introspectionURL, String clientId, String clientSecret,
|
|
|
|
String accessTokenJWTString) throws KeycloakClientException {
|
|
|
|
|
|
|
|
return introspectAccessToken(introspectionURL, clientId, clientSecret, accessTokenJWTString).isActive();
|
|
|
|
}
|
|
|
|
|
2021-05-28 18:29:06 +02:00
|
|
|
}
|