All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.quarkus.oidc.runtime.OidcProviderClient Maven / Gradle / Ivy

Go to download

Secure your applications with OpenID Connect Adapter and IDP such as Keycloak

There is a newer version: 3.17.5
Show newest version
package io.quarkus.oidc.runtime;

import java.io.Closeable;
import java.net.ConnectException;
import java.nio.charset.StandardCharsets;
import java.security.Key;
import java.util.Map;

import org.jboss.logging.Logger;

import io.quarkus.oidc.AuthorizationCodeTokens;
import io.quarkus.oidc.OIDCException;
import io.quarkus.oidc.OidcConfigurationMetadata;
import io.quarkus.oidc.OidcTenantConfig;
import io.quarkus.oidc.TokenIntrospection;
import io.quarkus.oidc.UserInfo;
import io.quarkus.oidc.common.runtime.OidcCommonUtils;
import io.quarkus.oidc.common.runtime.OidcConstants;
import io.quarkus.oidc.common.runtime.OidcEndpointAccessException;
import io.smallrye.mutiny.Uni;
import io.smallrye.mutiny.groups.UniOnItem;
import io.vertx.core.http.HttpHeaders;
import io.vertx.core.json.JsonObject;
import io.vertx.mutiny.core.MultiMap;
import io.vertx.mutiny.core.buffer.Buffer;
import io.vertx.mutiny.ext.web.client.HttpRequest;
import io.vertx.mutiny.ext.web.client.HttpResponse;
import io.vertx.mutiny.ext.web.client.WebClient;

public class OidcProviderClient implements Closeable {
    private static final Logger LOG = Logger.getLogger(OidcProviderClient.class);

    private static final String AUTHORIZATION_HEADER = String.valueOf(HttpHeaders.AUTHORIZATION);
    private static final String CONTENT_TYPE_HEADER = String.valueOf(HttpHeaders.CONTENT_TYPE);
    private static final String ACCEPT_HEADER = String.valueOf(HttpHeaders.ACCEPT);
    private static final String APPLICATION_X_WWW_FORM_URLENCODED = String
            .valueOf(HttpHeaders.APPLICATION_X_WWW_FORM_URLENCODED.toString());
    private static final String APPLICATION_JSON = "application/json";

    private final WebClient client;
    private final OidcConfigurationMetadata metadata;
    private final OidcTenantConfig oidcConfig;
    private final String clientSecretBasicAuthScheme;
    private final String introspectionBasicAuthScheme;
    private final Key clientJwtKey;

    public OidcProviderClient(WebClient client,
            OidcConfigurationMetadata metadata,
            OidcTenantConfig oidcConfig) {
        this.client = client;
        this.metadata = metadata;
        this.oidcConfig = oidcConfig;
        this.clientSecretBasicAuthScheme = OidcCommonUtils.initClientSecretBasicAuth(oidcConfig);
        this.clientJwtKey = OidcCommonUtils.initClientJwtKey(oidcConfig);
        this.introspectionBasicAuthScheme = initIntrospectionBasicAuthScheme(oidcConfig);
    }

    private static String initIntrospectionBasicAuthScheme(OidcTenantConfig oidcConfig) {
        if (oidcConfig.getIntrospectionCredentials().name.isPresent()
                && oidcConfig.getIntrospectionCredentials().secret.isPresent()) {
            return OidcCommonUtils.basicSchemeValue(oidcConfig.getIntrospectionCredentials().name.get(),
                    oidcConfig.getIntrospectionCredentials().secret.get());
        } else {
            return null;
        }
    }

    public OidcConfigurationMetadata getMetadata() {
        return metadata;
    }

    public Uni getJsonWebKeySet() {
        return client.getAbs(metadata.getJsonWebKeySetUri()).send().onItem()
                .transform(resp -> getJsonWebKeySet(resp));
    }

    public Uni getUserInfo(String token) {
        LOG.debugf("Get UserInfo on: %s auth: %s", metadata.getUserInfoUri(), OidcConstants.BEARER_SCHEME + " " + token);
        return client.getAbs(metadata.getUserInfoUri())
                .putHeader(AUTHORIZATION_HEADER, OidcConstants.BEARER_SCHEME + " " + token)
                .send().onItem().transform(resp -> getUserInfo(resp));
    }

    public Uni introspectToken(String token) {
        MultiMap introspectionParams = new MultiMap(io.vertx.core.MultiMap.caseInsensitiveMultiMap());
        introspectionParams.add(OidcConstants.INTROSPECTION_TOKEN, token);
        introspectionParams.add(OidcConstants.INTROSPECTION_TOKEN_TYPE_HINT, OidcConstants.ACCESS_TOKEN_VALUE);
        return getHttpResponse(metadata.getIntrospectionUri(), introspectionParams, true)
                .transform(resp -> getTokenIntrospection(resp));
    }

    private JsonWebKeySet getJsonWebKeySet(HttpResponse resp) {
        if (resp.statusCode() == 200) {
            return new JsonWebKeySet(resp.bodyAsString(StandardCharsets.UTF_8.name()));
        } else {
            throw new OidcEndpointAccessException(resp.statusCode());
        }
    }

    public OidcTenantConfig getOidcConfig() {
        return oidcConfig;
    }

    public Uni getAuthorizationCodeTokens(String code, String redirectUri, String codeVerifier) {
        MultiMap codeGrantParams = new MultiMap(io.vertx.core.MultiMap.caseInsensitiveMultiMap());
        codeGrantParams.add(OidcConstants.GRANT_TYPE, OidcConstants.AUTHORIZATION_CODE);
        codeGrantParams.add(OidcConstants.CODE_FLOW_CODE, code);
        codeGrantParams.add(OidcConstants.CODE_FLOW_REDIRECT_URI, redirectUri);
        if (codeVerifier != null) {
            codeGrantParams.add(OidcConstants.PKCE_CODE_VERIFIER, codeVerifier);
        }
        if (oidcConfig.codeGrant.extraParams != null) {
            codeGrantParams.addAll(oidcConfig.codeGrant.extraParams);
        }
        return getHttpResponse(metadata.getTokenUri(), codeGrantParams, false)
                .transform(resp -> getAuthorizationCodeTokens(resp));
    }

    public Uni refreshAuthorizationCodeTokens(String refreshToken) {
        MultiMap refreshGrantParams = new MultiMap(io.vertx.core.MultiMap.caseInsensitiveMultiMap());
        refreshGrantParams.add(OidcConstants.GRANT_TYPE, OidcConstants.REFRESH_TOKEN_GRANT);
        refreshGrantParams.add(OidcConstants.REFRESH_TOKEN_VALUE, refreshToken);
        return getHttpResponse(metadata.getTokenUri(), refreshGrantParams, false)
                .transform(resp -> getAuthorizationCodeTokens(resp));
    }

    private UniOnItem> getHttpResponse(String uri, MultiMap formBody, boolean introspect) {
        HttpRequest request = client.postAbs(uri);
        request.putHeader(CONTENT_TYPE_HEADER, APPLICATION_X_WWW_FORM_URLENCODED);
        request.putHeader(ACCEPT_HEADER, APPLICATION_JSON);
        if (oidcConfig.codeGrant.headers != null) {
            for (Map.Entry headerEntry : oidcConfig.codeGrant.headers.entrySet()) {
                request.putHeader(headerEntry.getKey(), headerEntry.getValue());
            }
        }
        if (introspect && introspectionBasicAuthScheme != null) {
            request.putHeader(AUTHORIZATION_HEADER, introspectionBasicAuthScheme);
            if (oidcConfig.clientId.isPresent() && oidcConfig.introspectionCredentials.includeClientId) {
                formBody.set(OidcConstants.CLIENT_ID, oidcConfig.clientId.get());
            }
        } else if (clientSecretBasicAuthScheme != null) {
            request.putHeader(AUTHORIZATION_HEADER, clientSecretBasicAuthScheme);
        } else if (clientJwtKey != null) {
            String jwt = OidcCommonUtils.signJwtWithKey(oidcConfig, metadata.getTokenUri(), clientJwtKey);
            if (OidcCommonUtils.isClientSecretPostJwtAuthRequired(oidcConfig.credentials)) {
                formBody.add(OidcConstants.CLIENT_ID, oidcConfig.clientId.get());
                formBody.add(OidcConstants.CLIENT_SECRET, jwt);
            } else {
                formBody.add(OidcConstants.CLIENT_ASSERTION_TYPE, OidcConstants.JWT_BEARER_CLIENT_ASSERTION_TYPE);
                formBody.add(OidcConstants.CLIENT_ASSERTION, jwt);
            }
        } else if (OidcCommonUtils.isClientSecretPostAuthRequired(oidcConfig.credentials)) {
            formBody.add(OidcConstants.CLIENT_ID, oidcConfig.clientId.get());
            formBody.add(OidcConstants.CLIENT_SECRET, OidcCommonUtils.clientSecret(oidcConfig.credentials));
        } else {
            formBody.add(OidcConstants.CLIENT_ID, oidcConfig.clientId.get());
        }
        LOG.debugf("Get token on: %s params: %s headers: %s", metadata.getTokenUri(), formBody, request.headers());
        // Retry up to three times with a one-second delay between the retries if the connection is closed.
        Uni> response = request.sendBuffer(OidcCommonUtils.encodeForm(formBody))
                .onFailure(ConnectException.class)
                .retry()
                .atMost(oidcConfig.connectionRetryCount).onFailure().transform(t -> t.getCause());
        return response.onItem();
    }

    private AuthorizationCodeTokens getAuthorizationCodeTokens(HttpResponse resp) {
        JsonObject json = getJsonObject(resp);
        final String idToken = json.getString(OidcConstants.ID_TOKEN_VALUE);
        final String accessToken = json.getString(OidcConstants.ACCESS_TOKEN_VALUE);
        final String refreshToken = json.getString(OidcConstants.REFRESH_TOKEN_VALUE);
        return new AuthorizationCodeTokens(idToken, accessToken, refreshToken);
    }

    private UserInfo getUserInfo(HttpResponse resp) {
        return new UserInfo(getString(resp));
    }

    private TokenIntrospection getTokenIntrospection(HttpResponse resp) {
        return new TokenIntrospection(getString(resp));
    }

    private static JsonObject getJsonObject(HttpResponse resp) {
        if (resp.statusCode() == 200) {
            LOG.debugf("Request succeeded: %s", resp.bodyAsJsonObject());
            return resp.bodyAsJsonObject();
        } else {
            throw responseException(resp);
        }
    }

    private static String getString(HttpResponse resp) {
        if (resp.statusCode() == 200) {
            LOG.debugf("Request succeeded: %s", resp.bodyAsString());
            return resp.bodyAsString();
        } else {
            throw responseException(resp);
        }
    }

    private static OIDCException responseException(HttpResponse resp) {
        String errorMessage = resp.bodyAsString();
        LOG.debugf("Request has failed: status: %d, error message: %s", resp.statusCode(), errorMessage);
        throw new OIDCException(errorMessage);
    }

    @Override
    public void close() {
        client.close();
    }

    public Key getClientJwtKey() {
        return clientJwtKey;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy