[#22515] Added refresh token methods
This commit is contained in:
parent
21774d9a91
commit
f5ef1d2c92
|
@ -2,5 +2,8 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
|
|||
|
||||
# Changelog for "keycloak-client"
|
||||
|
||||
## [v1.1.0-SNAPSHOT]
|
||||
|
||||
|
||||
## [v1.0.1]
|
||||
- First release (#21389 #22155) provides the basic helper classes for Keycloak tokens retrieve and functions for the gCube framework integration (automatic service discovery).
|
||||
|
|
2
pom.xml
2
pom.xml
|
@ -13,7 +13,7 @@
|
|||
|
||||
<groupId>org.gcube.common</groupId>
|
||||
<artifactId>keycloak-client</artifactId>
|
||||
<version>1.0.1</version>
|
||||
<version>1.1.0-SNAPSHOT</version>
|
||||
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
|
|
|
@ -2,6 +2,7 @@ package org.gcube.common.keycloak;
|
|||
|
||||
import static org.gcube.resources.discovery.icclient.ICFactory.clientFor;
|
||||
import static org.gcube.resources.discovery.icclient.ICFactory.queryFor;
|
||||
import static org.gcube.common.keycloak.model.OIDCConstants.*;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.MalformedURLException;
|
||||
|
@ -17,6 +18,7 @@ import java.util.stream.Collectors;
|
|||
|
||||
import org.gcube.common.gxrest.request.GXHTTPStringRequest;
|
||||
import org.gcube.common.gxrest.response.inbound.GXInboundResponse;
|
||||
import org.gcube.common.keycloak.model.ModelUtils;
|
||||
import org.gcube.common.keycloak.model.TokenResponse;
|
||||
import org.gcube.common.resources.gcore.ServiceEndpoint;
|
||||
import org.gcube.common.resources.gcore.ServiceEndpoint.AccessPoint;
|
||||
|
@ -26,11 +28,6 @@ import org.gcube.resources.discovery.client.queries.api.SimpleQuery;
|
|||
|
||||
public class DefaultKeycloakClient implements KeycloakClient {
|
||||
|
||||
private static final String PERMISSION_PARAMETER = "permission";
|
||||
private static final String GRANT_TYPE_PARAMETER = "grant_type";
|
||||
private static final String UMA_TOKEN_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:uma-ticket";
|
||||
private static final String AUDIENCE_PARAMETER = "audience";
|
||||
|
||||
@Override
|
||||
public URL findTokenEndpointURL() throws KeycloakClientException {
|
||||
logger.debug("Checking ScopeProvider's scope presence and format");
|
||||
|
@ -102,41 +99,38 @@ public class DefaultKeycloakClient implements KeycloakClient {
|
|||
List<String> permissions) throws KeycloakClientException {
|
||||
|
||||
if (tokenURL == null) {
|
||||
throw new KeycloakClientException("'tokenURL' parameter must be not null");
|
||||
throw new KeycloakClientException("Token URL must be not null");
|
||||
}
|
||||
|
||||
if (authorization == null || "".equals(authorization)) {
|
||||
throw new KeycloakClientException("'authorization' parameter must be not null nor empty");
|
||||
throw new KeycloakClientException("Authorization must be not null nor empty");
|
||||
}
|
||||
|
||||
if (audience == null || "".equals(audience)) {
|
||||
throw new KeycloakClientException("'audience' parameter must be not null nor empty");
|
||||
throw new KeycloakClientException("Audience must be not null nor empty");
|
||||
}
|
||||
|
||||
logger.debug("Querying 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("Cannot URL encode 'audience'", e);
|
||||
}
|
||||
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()));
|
||||
}
|
||||
|
||||
// Constructing request object
|
||||
GXHTTPStringRequest request;
|
||||
try {
|
||||
Map<String, List<String>> params = new HashMap<>();
|
||||
params.put(GRANT_TYPE_PARAMETER, Arrays.asList(UMA_TOKEN_GRANT_TYPE));
|
||||
|
||||
params.put(AUDIENCE_PARAMETER, Arrays.asList(URLEncoder.encode(checkAudience(audience), "UTF-8")));
|
||||
|
||||
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()));
|
||||
}
|
||||
|
||||
String queryString = params.entrySet().stream()
|
||||
.flatMap(p -> p.getValue().stream().map(v -> p.getKey() + "=" + v))
|
||||
.reduce((p1, p2) -> p1 + "&" + p2).orElse("");
|
||||
|
@ -168,7 +162,7 @@ public class DefaultKeycloakClient implements KeycloakClient {
|
|||
} else {
|
||||
throw KeycloakClientException.create("Unable to get token", response.getHTTPCode(),
|
||||
response.getHeaderFields()
|
||||
.getOrDefault("Content-Type", Collections.singletonList("unknown/unknown")).get(0),
|
||||
.getOrDefault("content-type", Collections.singletonList("unknown/unknown")).get(0),
|
||||
response.getMessage());
|
||||
}
|
||||
}
|
||||
|
@ -185,4 +179,113 @@ public class DefaultKeycloakClient implements KeycloakClient {
|
|||
return audience;
|
||||
}
|
||||
|
||||
@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());
|
||||
}
|
||||
|
||||
@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"));
|
||||
}
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ public interface KeycloakClient {
|
|||
|
||||
/**
|
||||
* Finds the keycloak endpoint {@link URL} discovering it in the current scope provided by {@link ScopeProvider}
|
||||
*
|
||||
* @return the keycloak endpoint URL in the current scope
|
||||
* @throws KeycloakClientException if something goes wrong discovering the endpoint URL
|
||||
*/
|
||||
|
@ -54,7 +55,7 @@ public interface KeycloakClient {
|
|||
throws KeycloakClientException;
|
||||
|
||||
/**
|
||||
* Queries an UMA token from the discovered Keycloak server in the current scope, by using provided clientId and client secret
|
||||
* Queries an UMA token from the Keycloak server discovered in the current scope, by using provided clientId and client secret
|
||||
* for the given audience (context), in URLEncoded form or not, and optionally a list of permissions.
|
||||
*
|
||||
* @param clientId the client id
|
||||
|
@ -68,7 +69,7 @@ public interface KeycloakClient {
|
|||
throws KeycloakClientException;
|
||||
|
||||
/**
|
||||
* Queries an UMA token from the discovered Keycloak server in the current scope, by using provided clientId and client secret
|
||||
* Queries an UMA token from the Keycloak server discovered in the current scope, by using provided clientId and client secret
|
||||
* for the current scope audience (context), in URLEncoded form or not, and optionally a list of permissions.
|
||||
*
|
||||
* @param clientId the client id
|
||||
|
@ -79,4 +80,114 @@ public interface KeycloakClient {
|
|||
*/
|
||||
TokenResponse queryUMAToken(String clientId, String clientSecret, List<String> permissions)
|
||||
throws KeycloakClientException;
|
||||
|
||||
/**
|
||||
* Refreshes a previously issued token from the Keycloak server discovered in the current scope using the refresh
|
||||
* token JWT encoded string in the token response object.
|
||||
*
|
||||
* Client id will be read from "issued for" access token's claim and client secret will be not sent.
|
||||
* <br><b>NOTE</b>: For <code>public</code> clients types only.
|
||||
*
|
||||
* @param tokenResponse the previously issued token as {@link TokenResponse} object
|
||||
* @return the refreshed token as {@link TokenResponse} object
|
||||
* @throws KeycloakClientException if something goes wrong performing the refresh query
|
||||
*/
|
||||
TokenResponse refreshToken(TokenResponse tokenResponse) throws KeycloakClientException;
|
||||
|
||||
/**
|
||||
* Refreshes a previously issued token from the Keycloak server using the refresh token JWT encoded string in the
|
||||
* token response object.
|
||||
*
|
||||
* Client id will be read from "issued for" access token's claim and client secret will be not sent.
|
||||
* <br><b>NOTE</b>: For <code>public</code> clients types only.
|
||||
*
|
||||
* @param tokenUrl the token endpoint {@link URL} of the OIDC server
|
||||
* @param tokenResponse the previously issued token as {@link TokenResponse} object
|
||||
* @return the refreshed token as {@link TokenResponse} object
|
||||
* @throws KeycloakClientException if something goes wrong performing the refresh query
|
||||
*/
|
||||
TokenResponse refreshToken(URL tokenURL, TokenResponse tokenResponse) throws KeycloakClientException;
|
||||
|
||||
/**
|
||||
* Refreshes a previously issued token from the Keycloak server discovered in the current scope using the refresh
|
||||
* token JWT encoded string in the token response object and the provided client id.
|
||||
*
|
||||
* Client secret will be not sent.
|
||||
* <br><b>NOTE</b>: For <code>public</code> clients types only.
|
||||
*
|
||||
* @param clientId the requestor client id, may be <code>null</code> and in this case will be take from the access token "issued for" claim
|
||||
* @param tokenResponse the previously issued token as {@link TokenResponse} object
|
||||
* @return the refreshed token as {@link TokenResponse} object
|
||||
* @throws KeycloakClientException if something goes wrong performing the refresh query
|
||||
*/
|
||||
TokenResponse refreshToken(String clientId, TokenResponse tokenResponse) throws KeycloakClientException;
|
||||
|
||||
/**
|
||||
* Refreshes a previously issued token from the Keycloak server discovered in the current scope using the refresh
|
||||
* token JWT encoded string in the token response object and the provided client id and secret.
|
||||
*
|
||||
* @param clientId the requestor client id, may be <code>null</code> and in this case will be take from the access token "issued for" claim
|
||||
* @param clientSecret the requestor client secret, may be <code>null</code> for non-confidential clients
|
||||
* @param tokenResponse the previously issued token as {@link TokenResponse} object
|
||||
* @return the refreshed token as {@link TokenResponse} object
|
||||
* @throws KeycloakClientException if something goes wrong performing the refresh query
|
||||
*/
|
||||
TokenResponse refreshToken(String clientId, String clientSecret, TokenResponse tokenResponse)
|
||||
throws KeycloakClientException;
|
||||
|
||||
/**
|
||||
* Refreshes a previously issued token from the Keycloak server using the refresh token JWT encoded string in the
|
||||
* token response object and the provided client id and secret.
|
||||
*
|
||||
* @param clientId the requestor client id, may be <code>null</code> and in this case will be take from the access token "issued for" claim
|
||||
* @param clientSecret the requestor client secret, may be <code>null</code> for non-confidential clients
|
||||
* @param tokenResponse the previously issued token as {@link TokenResponse} object
|
||||
* @return the refreshed token as {@link TokenResponse} object
|
||||
* @throws KeycloakClientException if something goes wrong performing the refresh query
|
||||
*/
|
||||
TokenResponse refreshToken(URL tokenURL, String clientId, String clientSecret, TokenResponse tokenResponse)
|
||||
throws KeycloakClientException;
|
||||
|
||||
/**
|
||||
* Refreshes a previously issued token from the Keycloak server discovered in the current scope using the provided
|
||||
* client id and the refresh token JWT encoded string obtained with the access token in the previous token response.
|
||||
*
|
||||
* Client secret will be not used.
|
||||
* <br><b>NOTE</b>: For <code>public</code> clients types only.
|
||||
*
|
||||
* @param clientId the requestor client id
|
||||
* @param refreshTokenJWTString the previously issued refresh token JWT string taken from the same token response of the access token parameter
|
||||
* @return the refreshed token as {@link TokenResponse} object
|
||||
* @throws KeycloakClientException if something goes wrong performing the refresh query
|
||||
*/
|
||||
TokenResponse refreshToken(String clientId, String refreshTokenJWTString) throws KeycloakClientException;
|
||||
|
||||
/**
|
||||
* Refreshes a previously issued token from the Keycloak server discovered in the current scope using the provided
|
||||
* client id and secret and the refresh token JWT encoded string obtained with the access token in the previous
|
||||
* token response.
|
||||
*
|
||||
* @param clientId the requestor client id
|
||||
* @param clientSecret the requestor client secret, may be <code>null</code> for non-confidential clients
|
||||
* @param refreshTokenJWTString the previously issued refresh token JWT string taken from the same token response of the access token parameter
|
||||
* @return the refreshed token as {@link TokenResponse} object
|
||||
* @throws KeycloakClientException if something goes wrong performing the refresh query
|
||||
*/
|
||||
TokenResponse refreshToken(String clientId, String clientSecret, String refreshTokenJWTString)
|
||||
throws KeycloakClientException;
|
||||
|
||||
/**
|
||||
* Refreshes a previously issued token from the Keycloak server by using the client id and secret
|
||||
* and the refresh token JWT encoded string obtained with the access token in the previous token response.
|
||||
*
|
||||
* @param tokenUrl the token endpoint {@link URL} of the OIDC server
|
||||
* @param clientId the requestor client id
|
||||
* @param clientSecret the requestor client secret, may be <code>null</code> for non-confidential clients
|
||||
* @param refreshTokenJWTString the previously issued refresh token JWT string
|
||||
* @return the refreshed token as {@link TokenResponse} object
|
||||
* @throws KeycloakClientException if something goes wrong performing the refresh query
|
||||
*/
|
||||
TokenResponse refreshToken(URL tokenURL, String clientId, String clientSecret, String refreshTokenJWTString)
|
||||
throws KeycloakClientException;
|
||||
|
||||
}
|
|
@ -35,7 +35,7 @@ public class KeycloakClientException extends Exception {
|
|||
super(message);
|
||||
}
|
||||
|
||||
public KeycloakClientException(String message, Exception cause) {
|
||||
public KeycloakClientException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
package org.gcube.common.keycloak.model;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Base64;
|
||||
import java.util.List;
|
||||
|
||||
import org.gcube.com.fasterxml.jackson.annotation.JsonInclude.Include;
|
||||
import org.gcube.com.fasterxml.jackson.core.JsonProcessingException;
|
||||
|
@ -13,6 +15,8 @@ public class ModelUtils {
|
|||
|
||||
protected static final Logger logger = LoggerFactory.getLogger(ModelUtils.class);
|
||||
|
||||
private static final String ACCOUNT_AUDIENCE_RESOURCE = "account";
|
||||
|
||||
private static final ObjectMapper mapper = new ObjectMapper();
|
||||
|
||||
static {
|
||||
|
@ -33,31 +37,40 @@ public class ModelUtils {
|
|||
}
|
||||
}
|
||||
|
||||
private static byte[] getDecodedPayload(String value) {
|
||||
return getBase64Decoded(getEncodedPayload(value));
|
||||
public static String getAccessTokenPayloadJSONStringFrom(TokenResponse tokenResponse) throws Exception {
|
||||
return getAccessTokenPayloadJSONStringFrom(tokenResponse, true);
|
||||
}
|
||||
|
||||
public static String getAccessTokenPayloadStringFrom(TokenResponse tokenResponse) throws Exception {
|
||||
return getAccessTokenPayloadStringFrom(tokenResponse, true);
|
||||
}
|
||||
|
||||
public static String getAccessTokenPayloadStringFrom(TokenResponse tokenResponse, boolean prettyPrint) throws Exception {
|
||||
public static String getAccessTokenPayloadJSONStringFrom(TokenResponse tokenResponse, boolean prettyPrint)
|
||||
throws Exception {
|
||||
return toJSONString(getAccessTokenFrom(tokenResponse, Object.class), prettyPrint);
|
||||
}
|
||||
|
||||
public static AccessToken getAccessTokenFrom(TokenResponse tokenResponse) throws Exception {
|
||||
return getAccessTokenFrom(tokenResponse, RefreshToken.class);
|
||||
return getAccessTokenFrom(tokenResponse, AccessToken.class);
|
||||
}
|
||||
|
||||
public static AccessToken getAccessTokenFrom(String authorizationHeaderOrBase64EncodedJWT) throws Exception {
|
||||
return getAccessTokenFrom(authorizationHeaderOrBase64EncodedJWT.matches("[b|B]earer ")
|
||||
? authorizationHeaderOrBase64EncodedJWT.substring("bearer ".length())
|
||||
: authorizationHeaderOrBase64EncodedJWT, AccessToken.class);
|
||||
}
|
||||
|
||||
private static <T> T getAccessTokenFrom(TokenResponse tokenResponse, Class<T> clazz) throws Exception {
|
||||
return mapper.readValue(getDecodedPayload(tokenResponse.getAccessToken()), clazz);
|
||||
return getAccessTokenFrom(tokenResponse.getAccessToken(), clazz);
|
||||
}
|
||||
|
||||
private static <T> T getAccessTokenFrom(String accessToken, Class<T> clazz) throws Exception {
|
||||
return mapper.readValue(getDecodedPayload(accessToken), clazz);
|
||||
}
|
||||
|
||||
public static String getRefreshTokenPayloadStringFrom(TokenResponse tokenResponse) throws Exception {
|
||||
return getRefreshTokenPayloadStringFrom(tokenResponse, true);
|
||||
}
|
||||
|
||||
public static String getRefreshTokenPayloadStringFrom(TokenResponse tokenResponse, boolean prettyPrint) throws Exception {
|
||||
public static String getRefreshTokenPayloadStringFrom(TokenResponse tokenResponse, boolean prettyPrint)
|
||||
throws Exception {
|
||||
|
||||
return toJSONString(getRefreshTokenFrom(tokenResponse, Object.class), prettyPrint);
|
||||
}
|
||||
|
||||
|
@ -82,16 +95,50 @@ public class ModelUtils {
|
|||
}
|
||||
}
|
||||
|
||||
public static byte[] getDecodedHeader(String value) {
|
||||
return getBase64Decoded(getEncodedHeader(value));
|
||||
}
|
||||
|
||||
public static String getEncodedHeader(String encodedJWT) {
|
||||
return splitAndGet(encodedJWT, 0);
|
||||
}
|
||||
|
||||
public static byte[] getDecodedPayload(String value) {
|
||||
return getBase64Decoded(getEncodedPayload(value));
|
||||
}
|
||||
|
||||
public static String getEncodedPayload(String encodedJWT) {
|
||||
return splitAndGet(encodedJWT, 1);
|
||||
}
|
||||
|
||||
public static byte[] getDecodedSignature(String value) {
|
||||
return getBase64Decoded(getEncodedSignature(value));
|
||||
}
|
||||
|
||||
public static String getEncodedSignature(String encodedJWT) {
|
||||
return splitAndGet(encodedJWT, 2);
|
||||
}
|
||||
|
||||
public static String getClientIdFromToken(AccessToken accessToken) {
|
||||
String clientId;
|
||||
logger.debug("Client id not provided, using authorized party field (azp)");
|
||||
clientId = accessToken.getIssuedFor();
|
||||
if (clientId == null) {
|
||||
logger.debug("Issued for field (azp) not present, getting first of the audience field (aud)");
|
||||
clientId = getFirstAudienceNoAccount(accessToken);
|
||||
}
|
||||
return clientId;
|
||||
}
|
||||
|
||||
private static String getFirstAudienceNoAccount(AccessToken accessToken) {
|
||||
// Trying to get it from the token's audience ('aud' field), getting the first except the 'account'
|
||||
List<String> tokenAud = Arrays.asList(accessToken.getAudience());
|
||||
tokenAud.remove(ACCOUNT_AUDIENCE_RESOURCE);
|
||||
if (tokenAud.size() > 0) {
|
||||
return tokenAud.iterator().next();
|
||||
} else {
|
||||
// Setting it to empty string to avoid NPE in encoding
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
package org.gcube.common.keycloak.model;
|
||||
|
||||
public class OIDCConstants {
|
||||
|
||||
public static final String PERMISSION_PARAMETER = "permission";
|
||||
public static final String GRANT_TYPE_PARAMETER = "grant_type";
|
||||
public static final String UMA_TOKEN_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:uma-ticket";
|
||||
public static final String AUDIENCE_PARAMETER = "audience";
|
||||
public static final String REFRESH_TOKEN_GRANT_TYPE = "refresh_token";
|
||||
public static final String REFRESH_TOKEN_PARAMETER = "refresh_token";
|
||||
public static final String CLIENT_ID_PARAMETER = "client_id";
|
||||
public static final String CLIENT_SECRET_PARAMETER = "client_secret";
|
||||
|
||||
}
|
|
@ -8,10 +8,13 @@ import org.gcube.common.scope.api.ScopeProvider;
|
|||
import org.junit.After;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.FixMethodOrder;
|
||||
import org.junit.Test;
|
||||
import org.junit.runners.MethodSorters;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
|
||||
public class TestKeycloakClient {
|
||||
|
||||
protected static final Logger logger = LoggerFactory.getLogger(TestKeycloakClient.class);
|
||||
|
@ -21,6 +24,8 @@ public class TestKeycloakClient {
|
|||
private static final String CLIENT_SECRET = "38f76152-2b7c-418f-9b67-66f4cc2f401e";
|
||||
private static final String TEST_AUDIENCE = "conductor-server";
|
||||
|
||||
private static TokenResponse tr = null;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
ScopeProvider.instance.set("/gcube");
|
||||
|
@ -31,7 +36,7 @@ public class TestKeycloakClient {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testEndpointDiscovery() throws Exception {
|
||||
public void test1EndpointDiscovery() throws Exception {
|
||||
logger.info("Start testing Keycloak endpoint discovery...");
|
||||
URL url = KeycloakClientFactory.newInstance().findTokenEndpointURL();
|
||||
Assert.assertNotNull(url);
|
||||
|
@ -40,31 +45,38 @@ public class TestKeycloakClient {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testQueryUMATokenWithDiscoveryInCurrentScope() throws Exception {
|
||||
public void test2QueryUMATokenWithDiscoveryInCurrentScope() throws Exception {
|
||||
logger.info("Start testing query UMA token from Keycloak with endpoint discovery and current scope...");
|
||||
TokenResponse tr = KeycloakClientFactory.newInstance().queryUMAToken(CLIENT_ID, CLIENT_SECRET, null);
|
||||
tr = KeycloakClientFactory.newInstance().queryUMAToken(CLIENT_ID, CLIENT_SECRET, null);
|
||||
TestModels.checkTokenResponse(tr);
|
||||
TestModels.checkAccessToken(ModelUtils.getAccessTokenFrom(tr), "service-account-" + CLIENT_ID);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testQueryUMATokenWithDiscovery() throws Exception {
|
||||
public void test3QueryUMATokenWithDiscovery() throws Exception {
|
||||
logger.info("Start testing query UMA token from Keycloak with endpoint discovery...");
|
||||
TokenResponse tr = KeycloakClientFactory.newInstance().queryUMAToken(CLIENT_ID, CLIENT_SECRET, TEST_AUDIENCE,
|
||||
null);
|
||||
tr = KeycloakClientFactory.newInstance().queryUMAToken(CLIENT_ID, CLIENT_SECRET, TEST_AUDIENCE, null);
|
||||
TestModels.checkTokenResponse(tr);
|
||||
TestModels.checkAccessToken(ModelUtils.getAccessTokenFrom(tr), "service-account-" + CLIENT_ID);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test4QueryUMAToken() throws Exception {
|
||||
logger.info("Start testing query UMA token from Keycloak with URL...");
|
||||
tr = KeycloakClientFactory.newInstance().queryUMAToken(new URL(DEV_ENDPOINT), CLIENT_ID, CLIENT_SECRET,
|
||||
TEST_AUDIENCE, null);
|
||||
|
||||
TestModels.checkTokenResponse(tr);
|
||||
TestModels.checkAccessToken(ModelUtils.getAccessTokenFrom(tr), "service-account-" + CLIENT_ID);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testQueryUMAToken() throws Exception {
|
||||
logger.info("Start testing query UMA token from Keycloak with URL...");
|
||||
TokenResponse tr = KeycloakClientFactory.newInstance()
|
||||
.queryUMAToken(new URL(DEV_ENDPOINT), CLIENT_ID, CLIENT_SECRET, TEST_AUDIENCE, null);
|
||||
|
||||
TestModels.checkTokenResponse(tr);
|
||||
TestModels.checkAccessToken(ModelUtils.getAccessTokenFrom(tr), "service-account-" + CLIENT_ID);
|
||||
public void test5RefreshTokenWithDiscovery() throws Exception {
|
||||
logger.info("Start testing refresh UMA token from Keycloak with endpoint discovery...");
|
||||
TokenResponse refreshedTR = KeycloakClientFactory.newInstance().refreshToken(CLIENT_ID, CLIENT_SECRET, tr);
|
||||
TestModels.checkTokenResponse(refreshedTR);
|
||||
TestModels.checkAccessToken(ModelUtils.getAccessTokenFrom(refreshedTR), "service-account-" + CLIENT_ID);
|
||||
TestModels.checkRefreshToken(ModelUtils.getRefreshTokenFrom(refreshedTR));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -3,17 +3,20 @@ package org.gcube.common.keycloak;
|
|||
import java.io.File;
|
||||
|
||||
import org.gcube.com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.gcube.common.keycloak.model.TokenResponse;
|
||||
import org.gcube.common.keycloak.model.AccessToken;
|
||||
import org.gcube.common.keycloak.model.ModelUtils;
|
||||
import org.gcube.common.keycloak.model.RefreshToken;
|
||||
import org.gcube.common.keycloak.model.TokenResponse;
|
||||
import org.junit.After;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.FixMethodOrder;
|
||||
import org.junit.Test;
|
||||
import org.junit.runners.MethodSorters;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
|
||||
public class TestModels {
|
||||
|
||||
protected static final Logger logger = LoggerFactory.getLogger(TestModels.class);
|
||||
|
@ -56,6 +59,22 @@ public class TestModels {
|
|||
checkAccessToken(at, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemoveBearerPrefixInHeader() throws Exception {
|
||||
TokenResponse tr = new ObjectMapper().readValue(new File("src/test/resources/oidc-token-response.json"),
|
||||
TokenResponse.class);
|
||||
|
||||
AccessToken at1 = ModelUtils.getAccessTokenFrom(tr.getAccessToken());
|
||||
AccessToken at2 = ModelUtils.getAccessTokenFrom("Bearer " + tr.getAccessToken());
|
||||
AccessToken at3 = ModelUtils.getAccessTokenFrom("bearer " + tr.getAccessToken());
|
||||
|
||||
checkAccessToken(at1, null);
|
||||
checkAccessToken(at2, null);
|
||||
checkAccessToken(at3, null);
|
||||
Assert.assertEquals(ModelUtils.toJSONString(at1), ModelUtils.toJSONString(at2));
|
||||
Assert.assertEquals(ModelUtils.toJSONString(at2), ModelUtils.toJSONString(at3));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUMARefreshToken() throws Exception {
|
||||
logger.info("Start testing refresh token object binding...");
|
||||
|
@ -86,4 +105,5 @@ public class TestModels {
|
|||
Assert.assertNotNull(rt.getOtherClaims());
|
||||
Assert.assertNotNull(rt.getAudience());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue