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

io.github.microcks.minion.async.client.KeycloakConnector Maven / Gradle / Ivy

/*
 * Licensed to Laurent Broudoux (the "Author") under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. Author licenses this
 * file to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package io.github.microcks.minion.async.client;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.ssl.TrustStrategy;
import org.apache.http.util.EntityUtils;
import org.eclipse.microprofile.config.inject.ConfigProperty;
import org.jboss.logging.Logger;

import javax.enterprise.context.ApplicationScoped;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.Base64;

@ApplicationScoped
/**
 * Connector to the Keycloak server configured for Microcks API server.
 * This connector allows to retrieved valid OAuth / JWT token for configured service account.
 * @author laurent
 */
public class KeycloakConnector {

   private final Logger logger = Logger.getLogger(getClass());

   @ConfigProperty(name = "microcks.serviceaccount")
   String serviceAccount;

   @ConfigProperty(name = "microcks.serviceaccount.credentials")
   String saCredentials;

   /**
    * Connect to the given OIDC endpoint on a Keycloak server, realize a client_credentials grant type
    * with configured account and credentials to finally return the OAuth accesc token.
    * @param tokenEndpoint The OIDC endpoint on which to authenticate
    * @return The OAuth access token after successful authentication
    * @throws ConnectorException If connection faild (bad endpoint) or authentication failed (bad account or credentials)
    * @throws IOException In case of communication exception
    */
   public String connectAndGetOAuthToken(String tokenEndpoint) throws ConnectorException, IOException {
      CloseableHttpClient httpClient = null;

      try {
         // Start creating a SSL Context that accepts all because we may have self-signed certs.
         TrustStrategy acceptingTrustStrategy = (cert, authType) -> true;
         SSLContext sslContext = SSLContexts.custom()
               .loadTrustMaterial(null, acceptingTrustStrategy)
               .build();
         SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext);

         // Configuring a httpClient that disables host name validation for certificates.
         httpClient = HttpClients.custom()
               .setSSLHostnameVerifier((String s, SSLSession sslSession) -> true)
               .setSSLSocketFactory(sslsf)
               .build();
      } catch (GeneralSecurityException gse) {
         logger.error("Caught a SecurityException when building the SSL Context", gse);
         throw new ConnectorException("SSLContext cannot be created to reach Keycloak endpoint: " + gse.getMessage());
      }

      try {
         // Prepare a post request with grant_type client credentials flow.
         HttpPost tokenRequest = new HttpPost(tokenEndpoint);
         tokenRequest.addHeader("Content-Type", "application/x-www-form-urlencoded");
         tokenRequest.addHeader("Accept", "application/json");
         tokenRequest.addHeader("Authorization", "Basic "
               + Base64.getEncoder().encodeToString(
               (serviceAccount + ":" + saCredentials).getBytes("UTF-8")
         ));
         tokenRequest.setEntity(new StringEntity("grant_type=client_credentials"));

         // Execute request and retrieve content as string.
         CloseableHttpResponse tokenResponse = httpClient.execute(tokenRequest);

         if (tokenResponse.getStatusLine().getStatusCode() != 200) {
            logger.error("OAuth token cannot be retrieved for Keycloak server, check microcks.serviceaccount configuration");
            logger.error("  tokenResponse.statusLine: " + tokenResponse.getStatusLine().toString());
            logger.error("  tokenResponse.statusCode: " + tokenResponse.getStatusLine().getStatusCode());
            throw new ConnectorException("OAuth token cannot be retrieved for Microcks. Check microcks.serviceaccount.");
         }

         String result = EntityUtils.toString(tokenResponse.getEntity());
         logger.debug("Result: " + result);

         // This should be a JSON token so parse it with Jackson.
         ObjectMapper mapper = new ObjectMapper();
         JsonNode jsonToken = mapper.readTree(result);

         // Retrieve and return access_token.
         String accessToken = jsonToken.path("access_token").asText();
         logger.debug("Got an access token: " + accessToken);
         return accessToken;
      } finally {
         httpClient.close();
      }
   }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy