
de.adorsys.ledgers.token.exchange.ConfigurableTokenResourceProvider Maven / Gradle / Ivy
package de.adorsys.ledgers.token.exchange;
import org.jboss.logging.Logger;
import org.jboss.resteasy.spi.HttpRequest;
import org.keycloak.OAuthErrorException;
import org.keycloak.TokenVerifier;
import org.keycloak.common.VerificationException;
import org.keycloak.crypto.SignatureProvider;
import org.keycloak.crypto.SignatureVerifierContext;
import org.keycloak.events.EventBuilder;
import org.keycloak.models.*;
import org.keycloak.protocol.oidc.TokenManager;
import org.keycloak.representations.AccessToken;
import org.keycloak.representations.AccessTokenResponse;
import org.keycloak.services.ErrorResponse;
import org.keycloak.services.Urls;
import org.keycloak.services.managers.AppAuthManager;
import org.keycloak.services.managers.AuthenticationManager;
import org.keycloak.services.resource.RealmResourceProvider;
import org.keycloak.services.resources.Cors;
import javax.ws.rs.Consumes;
import javax.ws.rs.OPTIONS;
import javax.ws.rs.POST;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Response;
import static javax.ws.rs.core.HttpHeaders.AUTHORIZATION;
import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
import static javax.ws.rs.core.MediaType.APPLICATION_JSON_TYPE;
import static javax.ws.rs.core.Response.Status.BAD_REQUEST;
import static org.keycloak.services.resources.Cors.ACCESS_CONTROL_ALLOW_METHODS;
import static org.keycloak.services.resources.Cors.ACCESS_CONTROL_ALLOW_ORIGIN;
import static org.keycloak.services.util.DefaultClientSessionContext.fromClientSessionScopeParameter;
/**
* @author Lorent Lempereur
*/
@SuppressWarnings("PMD")
public class ConfigurableTokenResourceProvider implements RealmResourceProvider {
static final String ID = "configurable-token";
private static final Logger LOG = Logger.getLogger(ConfigurableTokenResourceProvider.class);
private final KeycloakSession session;
private final TokenManager tokenManager;
ConfigurableTokenResourceProvider(KeycloakSession session) {
this.session = session;
this.tokenManager = new TokenManager();
}
@Override
public Object getResource() {
return this;
}
@Override
public void close() {
//This is a generated stub
}
@OPTIONS
public Response preflight(@Context HttpRequest request) {
return Cors.add(request, Response.ok()).auth().preflight().allowedMethods("POST", "OPTIONS").build();
}
@POST
@Consumes(APPLICATION_JSON)
@Produces(APPLICATION_JSON)
public Response createToken(TokenConfiguration tokenConfiguration, @Context HttpRequest request) {
try {
AccessToken accessToken = validateTokenAndUpdateSession(request);
UserSessionModel userSession = this.findSession();
AccessTokenResponse response = this.createAccessToken(userSession, accessToken, tokenConfiguration);
return this.buildCorsResponse(request, response);
} catch (ConfigurableTokenException e) {
LOG.error("An error occurred when fetching an access token", e);
return ErrorResponse.error(e.getMessage(), BAD_REQUEST);
}
}
private AccessTokenResponse createAccessToken(UserSessionModel userSession,
AccessToken accessToken,
TokenConfiguration tokenConfiguration) {
RealmModel realm = this.session.getContext().getRealm();
ClientModel client = realm.getClientByClientId(accessToken.getIssuedFor());
LOG.infof("Configurable token requested for username=%s and client=%s on realm=%s", userSession.getUser().getUsername(), client.getClientId(), realm.getName());
AuthenticatedClientSessionModel clientSession = userSession.getAuthenticatedClientSessionByClient(client.getId());
ClientSessionContext clientSessionContext = fromClientSessionScopeParameter(clientSession, session);
AccessToken newToken = tokenManager.createClientAccessToken(session, realm, client, userSession.getUser(), userSession, clientSessionContext);
updateTokenExpiration(newToken, tokenConfiguration);
updateScope(newToken, tokenConfiguration);
return buildResponse(realm, userSession, client, clientSession, newToken);
}
private AccessToken validateTokenAndUpdateSession(HttpRequest request) throws ConfigurableTokenException {
try {
RealmModel realm = session.getContext().getRealm();
String tokenString = readAccessTokenFrom(request);
TokenVerifier verifier = TokenVerifier.create(tokenString, AccessToken.class).withChecks(
TokenVerifier.IS_ACTIVE,
new TokenVerifier.RealmUrlCheck(Urls.realmIssuer(session.getContext().getUri().getBaseUri(), realm.getName()))
);
SignatureVerifierContext verifierContext = session.getProvider(SignatureProvider.class, verifier.getHeader().getAlgorithm().name()).verifier(verifier.getHeader().getKeyId());
verifier.verifierContext(verifierContext);
AccessToken accessToken = verifier.verify().getToken();
if (!tokenManager.checkTokenValidForIntrospection(session, realm, accessToken)) {
throw new VerificationException("introspection_failed");
}
return accessToken;
} catch (ConfigurableTokenException e) {
throw e;
} catch (VerificationException | OAuthErrorException e) {
LOG.warn("Keycloak-ConfigurableToken: introspection of token failed", e);
throw new ConfigurableTokenException("access_token_introspection_failed: " + e.getMessage());
}
}
private String readAccessTokenFrom(HttpRequest request) throws ConfigurableTokenException {
String authorization = request.getHttpHeaders().getHeaderString(AUTHORIZATION);
if (authorization == null || !authorization.startsWith("Bearer ")) {
LOG.warn("Keycloak-ConfigurableToken: no authorization header with bearer token");
throw new ConfigurableTokenException("bearer_token_missing_in_authorization_header");
}
String token = authorization.substring(7);
if (token.isEmpty()) {
LOG.warn("Keycloak-ConfigurableToken: empty access token");
throw new ConfigurableTokenException("missing_access_token");
}
return token;
}
private UserSessionModel findSession() throws ConfigurableTokenException {
RealmModel realm = session.getContext().getRealm();
AuthenticationManager.AuthResult authenticated = new AppAuthManager().authenticateBearerToken(session, realm);
if (authenticated == null) {
LOG.warn("Keycloak-ConfigurableToken: user not authenticated");
throw new ConfigurableTokenException("not_authenticated");
}
if (authenticated.getToken().getRealmAccess() == null) {
LOG.warn("Keycloak-ConfigurableToken: no realm associated with authorization");
throw new ConfigurableTokenException("wrong_realm");
}
UserModel user = authenticated.getUser();
if (user == null || !user.isEnabled()) {
LOG.warn("Keycloak-ConfigurableToken: user does not exist or is not enabled");
throw new ConfigurableTokenException("invalid_user");
}
UserSessionModel userSession = authenticated.getSession();
if (userSession == null) {
LOG.warn("Keycloak-ConfigurableToken: user does not have any active session");
throw new ConfigurableTokenException("missing_user_session");
}
return userSession;
}
private Response buildCorsResponse(@Context HttpRequest request, AccessTokenResponse response) {
Cors cors = Cors.add(request)
.auth()
.allowedMethods("POST")
.auth()
.exposedHeaders(ACCESS_CONTROL_ALLOW_METHODS, ACCESS_CONTROL_ALLOW_ORIGIN)
.allowAllOrigins();
return cors.builder(Response.ok(response).type(APPLICATION_JSON_TYPE)).build();
}
private AccessTokenResponse buildResponse(RealmModel realm,
UserSessionModel userSession,
ClientModel client,
AuthenticatedClientSessionModel clientSession,
AccessToken token) {
EventBuilder eventBuilder = new EventBuilder(realm, session, session.getContext().getConnection());
ClientSessionContext clientSessionContext = fromClientSessionScopeParameter(clientSession, session);
return tokenManager.responseBuilder(realm, client, eventBuilder, session, userSession, clientSessionContext)
.accessToken(token)
.build();
}
private void updateTokenExpiration(AccessToken token, TokenConfiguration tokenConfiguration) {
token.expiration(tokenConfiguration.computeTokenExpiration(token.getExpiration(), true));
}
private void updateScope(AccessToken token, TokenConfiguration tokenConfiguration) {
String offlineAccess = token.getScope().contains("offline_access") ? " offline_access" : "";
String updatedScope = token.getScope() + " " + tokenConfiguration.getScope() + offlineAccess;
token.setScope(updatedScope.trim());
}
static class ConfigurableTokenException extends Exception {
public ConfigurableTokenException(String message) {
super(message);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy