diff --git a/client/src/main/java/com/networknt/client/ClientConfig.java b/client/src/main/java/com/networknt/client/ClientConfig.java index f026279e7c..28692c6004 100644 --- a/client/src/main/java/com/networknt/client/ClientConfig.java +++ b/client/src/main/java/com/networknt/client/ClientConfig.java @@ -11,6 +11,8 @@ public final class ClientConfig { public static final String CONFIG_SECRET = "secret"; public static final String REQUEST = "request"; public static final String SERVER_URL = "server_url"; + public static final String PROXY_HOST = "proxyHost"; + public static final String PROXY_PORT = "proxyPort"; public static final String SERVICE_ID = "serviceId"; public static final String URI = "uri"; public static final String CLIENT_ID = "client_id"; diff --git a/client/src/main/java/com/networknt/client/http/HttpRequestValue.java b/client/src/main/java/com/networknt/client/http/HttpRequestValue.java index e12c787c90..c74692d792 100644 --- a/client/src/main/java/com/networknt/client/http/HttpRequestValue.java +++ b/client/src/main/java/com/networknt/client/http/HttpRequestValue.java @@ -52,6 +52,8 @@ public BodyPart getBody(String key) { /** * Indicates whether this entity has a body part by the key. + * @param key the key + * @return true if has body */ public boolean hasBody(String key) { return (this.bodyPartMap==null? false : this.bodyPartMap.containsKey(key) ); diff --git a/client/src/main/java/com/networknt/client/oauth/AuthorizationCodeRequest.java b/client/src/main/java/com/networknt/client/oauth/AuthorizationCodeRequest.java index 2e23cadc12..2097eb60e8 100644 --- a/client/src/main/java/com/networknt/client/oauth/AuthorizationCodeRequest.java +++ b/client/src/main/java/com/networknt/client/oauth/AuthorizationCodeRequest.java @@ -41,6 +41,9 @@ public AuthorizationCodeRequest() { Map tokenConfig = ClientConfig.get().getTokenConfig(); if(tokenConfig != null) { setServerUrl((String)tokenConfig.get(ClientConfig.SERVER_URL)); + setProxyHost((String)tokenConfig.get(ClientConfig.PROXY_HOST)); + int port = tokenConfig.get(ClientConfig.PROXY_PORT) == null ? 443 : (Integer)tokenConfig.get(ClientConfig.PROXY_PORT); + setProxyPort(port); setServiceId((String)tokenConfig.get(ClientConfig.SERVICE_ID)); Object object = tokenConfig.get(ClientConfig.ENABLE_HTTP2); setEnableHttp2(object != null && (Boolean) object); diff --git a/client/src/main/java/com/networknt/client/oauth/ClientAuthenticatedUserRequest.java b/client/src/main/java/com/networknt/client/oauth/ClientAuthenticatedUserRequest.java index 800a83b96c..7676e0cdda 100644 --- a/client/src/main/java/com/networknt/client/oauth/ClientAuthenticatedUserRequest.java +++ b/client/src/main/java/com/networknt/client/oauth/ClientAuthenticatedUserRequest.java @@ -17,6 +17,9 @@ public class ClientAuthenticatedUserRequest extends TokenRequest { /** * load default values from client.yml for client authenticated user grant, overwrite by setters * in case you want to change it at runtime. + * @param userType user type + * @param userId user id + * @param roles user roles */ public ClientAuthenticatedUserRequest(String userType, String userId, String roles) { setGrantType(ClientConfig.CLIENT_AUTHENTICATED_USER); @@ -26,6 +29,9 @@ public ClientAuthenticatedUserRequest(String userType, String userId, String rol Map tokenConfig = ClientConfig.get().getTokenConfig(); if(tokenConfig != null) { setServerUrl((String)tokenConfig.get(ClientConfig.SERVER_URL)); + setProxyHost((String)tokenConfig.get(ClientConfig.PROXY_HOST)); + int port = tokenConfig.get(ClientConfig.PROXY_PORT) == null ? 443 : (Integer)tokenConfig.get(ClientConfig.PROXY_PORT); + setProxyPort(port); setServiceId((String)tokenConfig.get(ClientConfig.SERVICE_ID)); Object object = tokenConfig.get(ClientConfig.ENABLE_HTTP2); setEnableHttp2(object != null && (Boolean) object); diff --git a/client/src/main/java/com/networknt/client/oauth/ClientCredentialsRequest.java b/client/src/main/java/com/networknt/client/oauth/ClientCredentialsRequest.java index 6544792dd5..ae71686afb 100644 --- a/client/src/main/java/com/networknt/client/oauth/ClientCredentialsRequest.java +++ b/client/src/main/java/com/networknt/client/oauth/ClientCredentialsRequest.java @@ -41,6 +41,9 @@ public ClientCredentialsRequest() { Map tokenConfig = ClientConfig.get().getTokenConfig(); if(tokenConfig != null) { setServerUrl((String)tokenConfig.get(ClientConfig.SERVER_URL)); + setProxyHost((String)tokenConfig.get(ClientConfig.PROXY_HOST)); + int port = tokenConfig.get(ClientConfig.PROXY_PORT) == null ? 443 : (Integer)tokenConfig.get(ClientConfig.PROXY_PORT); + setProxyPort(port); setServiceId((String)tokenConfig.get(ClientConfig.SERVICE_ID)); Object object = tokenConfig.get(ClientConfig.ENABLE_HTTP2); setEnableHttp2(object != null && (Boolean) object); diff --git a/client/src/main/java/com/networknt/client/oauth/ClientRequestComposerProvider.java b/client/src/main/java/com/networknt/client/oauth/ClientRequestComposerProvider.java index 9b1d30d362..14af25a639 100644 --- a/client/src/main/java/com/networknt/client/oauth/ClientRequestComposerProvider.java +++ b/client/src/main/java/com/networknt/client/oauth/ClientRequestComposerProvider.java @@ -1,13 +1,13 @@ package com.networknt.client.oauth; import com.networknt.client.Http2Client; -import io.undertow.client.ClientRequest; import io.undertow.util.Headers; -import io.undertow.util.Methods; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.UnsupportedEncodingException; +import java.net.URI; +import java.net.http.HttpRequest; import java.util.HashMap; import java.util.Map; @@ -77,15 +77,15 @@ public void registerComposer(ClientRequestComposers composerName, IClientRequest private static class DefaultSAMLBearerRequestComposer implements IClientRequestComposable { @Override - public ClientRequest composeClientRequest(TokenRequest tokenRequest) { - ClientRequest request = new ClientRequest().setMethod(Methods.POST).setPath(tokenRequest.getUri()); - request.getRequestHeaders().put(Headers.HOST, "localhost"); - request.getRequestHeaders().put(Headers.TRANSFER_ENCODING, "chunked"); - request.getRequestHeaders().put(Headers.CONTENT_TYPE, "application/x-www-form-urlencoded"); + public HttpRequest composeClientRequest(TokenRequest tokenRequest) { + final HttpRequest request = HttpRequest.newBuilder() + .POST(HttpRequest.BodyPublishers.ofString(composeRequestBody(tokenRequest))) + .uri(URI.create(tokenRequest.getServerUrl() + tokenRequest.getUri())) + .header(Headers.CONTENT_TYPE_STRING, "application/x-www-form-urlencoded") + .build(); return request; } - @Override public String composeRequestBody(TokenRequest tokenRequest) { SAMLBearerRequest SamlTokenRequest = (SAMLBearerRequest)tokenRequest; Map postBody = new HashMap<>(); @@ -108,16 +108,16 @@ public String composeRequestBody(TokenRequest tokenRequest) { private static class DefaultClientCredentialRequestComposer implements IClientRequestComposable { @Override - public ClientRequest composeClientRequest(TokenRequest tokenRequest) { - final ClientRequest request = new ClientRequest().setMethod(Methods.POST).setPath(tokenRequest.getUri()); - request.getRequestHeaders().put(Headers.HOST, "localhost"); - request.getRequestHeaders().put(Headers.TRANSFER_ENCODING, "chunked"); - request.getRequestHeaders().put(Headers.CONTENT_TYPE, "application/x-www-form-urlencoded"); - request.getRequestHeaders().put(Headers.AUTHORIZATION, OauthHelper.getBasicAuthHeader(tokenRequest.getClientId(), tokenRequest.getClientSecret())); + public HttpRequest composeClientRequest(TokenRequest tokenRequest) { + final HttpRequest request = HttpRequest.newBuilder() + .POST(HttpRequest.BodyPublishers.ofString(composeRequestBody(tokenRequest))) + .uri(URI.create(tokenRequest.getServerUrl() + tokenRequest.getUri())) + .setHeader(Headers.CONTENT_TYPE_STRING, "application/x-www-form-urlencoded") + .setHeader(Headers.AUTHORIZATION_STRING, OauthHelper.getBasicAuthHeader(tokenRequest.getClientId(), tokenRequest.getClientSecret())) + .build(); return request; } - @Override public String composeRequestBody(TokenRequest tokenRequest) { try { return OauthHelper.getEncodedString(tokenRequest); @@ -134,16 +134,17 @@ public String composeRequestBody(TokenRequest tokenRequest) { private static class DefaultClientAuthenticatedUserRequestComposer implements IClientRequestComposable { @Override - public ClientRequest composeClientRequest(TokenRequest tokenRequest) { - final ClientRequest request = new ClientRequest().setMethod(Methods.POST).setPath(tokenRequest.getUri()); - request.getRequestHeaders().put(Headers.HOST, "localhost"); - request.getRequestHeaders().put(Headers.TRANSFER_ENCODING, "chunked"); - request.getRequestHeaders().put(Headers.CONTENT_TYPE, "application/x-www-form-urlencoded"); - request.getRequestHeaders().put(Headers.AUTHORIZATION, OauthHelper.getBasicAuthHeader(tokenRequest.getClientId(), tokenRequest.getClientSecret())); + public HttpRequest composeClientRequest(TokenRequest tokenRequest) { + final HttpRequest request = HttpRequest.newBuilder() + .POST(HttpRequest.BodyPublishers.ofString(composeRequestBody(tokenRequest))) + .uri(URI.create(tokenRequest.getServerUrl() + tokenRequest.getUri())) + .setHeader(Headers.CONTENT_TYPE_STRING, "application/x-www-form-urlencoded") + .setHeader(Headers.AUTHORIZATION_STRING, OauthHelper.getBasicAuthHeader(tokenRequest.getClientId(), tokenRequest.getClientSecret())) + .build(); + return request; } - @Override public String composeRequestBody(TokenRequest tokenRequest) { try { return OauthHelper.getEncodedString(tokenRequest); @@ -153,5 +154,4 @@ public String composeRequestBody(TokenRequest tokenRequest) { return ""; } } - } diff --git a/client/src/main/java/com/networknt/client/oauth/DerefRequest.java b/client/src/main/java/com/networknt/client/oauth/DerefRequest.java index 4605beac9f..585d82a395 100644 --- a/client/src/main/java/com/networknt/client/oauth/DerefRequest.java +++ b/client/src/main/java/com/networknt/client/oauth/DerefRequest.java @@ -24,50 +24,9 @@ import java.util.Map; public class DerefRequest { - - /** - * @deprecated will be move to {@link ClientConfig#OAUTH} - */ - @Deprecated - public static String OAUTH = "oauth"; - - /** - * @deprecated will be move to {@link ClientConfig#DEREF} - */ - @Deprecated - public static String DEREF = "deref"; - - /** - * @deprecated will be move to {@link ClientConfig#SERVER_URL} - */ - @Deprecated - public static String SERVER_URL = "server_url"; - - /** - * @deprecated will be move to {@link ClientConfig#SERVICE_ID} - */ - @Deprecated - public static String SERVICE_ID = "serviceId"; - - /** - * @deprecated will be move to {@link ClientConfig#URI} - */ - @Deprecated - public static String URI = "uri"; - - /** - * @deprecated will be move to {@link ClientConfig#CLIENT_ID} - */ - @Deprecated - public static String CLIENT_ID = "client_id"; - - /** - * @deprecated will be move to {@link ClientConfig#ENABLE_HTTP2} - */ - @Deprecated - public static String ENABLE_HTTP2 = "enableHttp2"; - private String serverUrl; + private String proxyHost; + private int proxyPort; private String serviceId; private String uri; private String clientId; @@ -78,6 +37,9 @@ public DerefRequest(String token) { Map derefConfig = ClientConfig.get().getDerefConfig(); if(derefConfig != null) { setServerUrl((String)derefConfig.get(ClientConfig.SERVER_URL)); + setProxyHost((String)derefConfig.get(ClientConfig.PROXY_HOST)); + int port = derefConfig.get(ClientConfig.PROXY_PORT) == null ? 443 : (Integer)derefConfig.get(ClientConfig.PROXY_PORT); + setProxyPort(port); setServiceId((String)derefConfig.get(ClientConfig.SERVICE_ID)); Object object = derefConfig.get(ClientConfig.ENABLE_HTTP2); setEnableHttp2(object != null && (Boolean) object); @@ -136,4 +98,20 @@ public void setServiceId(String serviceId) { public boolean isEnableHttp2() { return enableHttp2; } public void setEnableHttp2(boolean enableHttp2) { this.enableHttp2 = enableHttp2; } + + public String getProxyHost() { + return proxyHost; + } + + public void setProxyHost(String proxyHost) { + this.proxyHost = proxyHost; + } + + public int getProxyPort() { + return proxyPort; + } + + public void setProxyPort(int proxyPort) { + this.proxyPort = proxyPort; + } } diff --git a/client/src/main/java/com/networknt/client/oauth/IClientRequestComposable.java b/client/src/main/java/com/networknt/client/oauth/IClientRequestComposable.java index 91d1c0619d..25fc03d0a6 100644 --- a/client/src/main/java/com/networknt/client/oauth/IClientRequestComposable.java +++ b/client/src/main/java/com/networknt/client/oauth/IClientRequestComposable.java @@ -1,23 +1,20 @@ package com.networknt.client.oauth; -import io.undertow.client.ClientRequest; + +import java.net.http.HttpRequest; /** - * An interface to describe that a ClientRequest can be composed by a TokenRequest. - * TokenRequest info should be the same for different Oauth servers, but different Oauth servers may have different way to accept request. + * An interface to describe that a HttpRequest can be composed by a TokenRequest. TokenRequest info should be the + * same for different OAuth servers, but different OAuth servers may have different way to accept request. + * + * @author Steve Hu */ public interface IClientRequestComposable { /** - * compose an actual ClientRequest based on the given TokenRequest model. + * compose an actual HttpRequest based on the given TokenRequest model. * @param tokenRequest token request - * @return ClientRequest + * @return HttpRequest */ - ClientRequest composeClientRequest(TokenRequest tokenRequest); + HttpRequest composeClientRequest(TokenRequest tokenRequest); - /** - * compose an actual request body based on the given TokenRequest model. - * @param tokenRequest token request - * @return String - */ - String composeRequestBody(TokenRequest tokenRequest); } diff --git a/client/src/main/java/com/networknt/client/oauth/KeyRequest.java b/client/src/main/java/com/networknt/client/oauth/KeyRequest.java index bd09780c41..a9785aa476 100644 --- a/client/src/main/java/com/networknt/client/oauth/KeyRequest.java +++ b/client/src/main/java/com/networknt/client/oauth/KeyRequest.java @@ -28,56 +28,9 @@ * */ public class KeyRequest { - - /** - * @deprecated will be move to {@link ClientConfig#OAUTH} - */ - @Deprecated - public static String OAUTH = "oauth"; - - /** - * @deprecated will be move to {@link ClientConfig#KEY} - */ - @Deprecated - public static String KEY = "key"; - - /** - * @deprecated will be move to {@link ClientConfig#SERVER_URL} - */ - @Deprecated - public static String SERVER_URL = "server_url"; - - /** - * @deprecated will be move to {@link ClientConfig#SERVICE_ID} - */ - @Deprecated - public static String SERVICE_ID = "serviceId"; - - /** - * @deprecated will be move to {@link ClientConfig#URI} - */ - @Deprecated - public static String URI = "uri"; - - /** - * @deprecated will be move to {@link ClientConfig#CLIENT_ID} - */ - @Deprecated - public static String CLIENT_ID = "client_id"; - - /** - * @deprecated will be move to {@link ClientConfig#CLIENT_SECRET} - */ - @Deprecated - public static String CLIENT_SECRET = "client_secret"; - - /** - * @deprecated will be move to {@link ClientConfig#ENABLE_HTTP2} - */ - @Deprecated - public static String ENABLE_HTTP2 = "enableHttp2"; - protected String serverUrl; + protected String proxyHost; + protected int proxyPort; protected String serviceId; protected String uri; protected String clientId; @@ -140,4 +93,20 @@ public String getKid() { public void setKid(String kid) { this.kid = kid; } + + public String getProxyHost() { + return proxyHost; + } + + public void setProxyHost(String proxyHost) { + this.proxyHost = proxyHost; + } + + public int getProxyPort() { + return proxyPort; + } + + public void setProxyPort(int proxyPort) { + this.proxyPort = proxyPort; + } } diff --git a/client/src/main/java/com/networknt/client/oauth/OauthHelper.java b/client/src/main/java/com/networknt/client/oauth/OauthHelper.java index e95d2b9094..f67a6f59be 100644 --- a/client/src/main/java/com/networknt/client/oauth/OauthHelper.java +++ b/client/src/main/java/com/networknt/client/oauth/OauthHelper.java @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package com.networknt.client.oauth; import com.fasterxml.jackson.core.JsonProcessingException; @@ -29,30 +28,27 @@ import com.networknt.monad.Success; import com.networknt.service.SingletonServiceFactory; import com.networknt.status.Status; -import com.networknt.utility.StringUtils; -import io.undertow.UndertowOptions; -import io.undertow.client.*; import io.undertow.server.HttpServerExchange; import io.undertow.util.*; import org.apache.commons.codec.binary.Base64; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.xnio.IoUtils; -import org.xnio.OptionMap; import java.io.IOException; import java.io.UnsupportedEncodingException; +import java.net.InetSocketAddress; +import java.net.ProxySelector; import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpHeaders; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.time.Duration; import java.util.ArrayList; import java.util.HashMap; import java.util.Map; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicReference; - -import static com.networknt.client.oauth.TokenRequest.*; +import java.util.Optional; +import java.util.concurrent.*; import static java.nio.charset.StandardCharsets.UTF_8; public class OauthHelper { @@ -62,40 +58,18 @@ public class OauthHelper { private static final String USER_ID = "userId"; private static final String USER_TYPE = "userType"; private static final String ROLES = "roles"; - - /** - * @deprecated will be moved to {@link ClientConfig#SCOPE} - */ - @Deprecated - static final String SCOPE = "scope"; - - /** - * @deprecated will be moved to {@link ClientConfig#SERVICE_ID} - */ - @Deprecated - static final String SERVICE_ID = "service_id"; private static final String FAIL_TO_SEND_REQUEST = "ERR10051"; private static final String GET_TOKEN_ERROR = "ERR10052"; private static final String ESTABLISH_CONNECTION_ERROR = "ERR10053"; private static final String GET_TOKEN_TIMEOUT = "ERR10054"; + private static final String TLS_TRUSTSTORE_ERROR = "ERR10055"; public static final String STATUS_CLIENT_CREDENTIALS_TOKEN_NOT_AVAILABLE = "ERR10009"; private static final Logger logger = LoggerFactory.getLogger(OauthHelper.class); - /** - * @deprecated As of release 1.5.29, replaced with @link #getTokenResult(TokenRequest tokenRequest) - * @param tokenRequest Request details for the token - * @return A TokenResponse on success - * @throws ClientException If any issues - */ - @Deprecated - public static TokenResponse getToken(TokenRequest tokenRequest) throws ClientException { - Result responseResult = getTokenResult(tokenRequest); - if (responseResult.isSuccess()) { - return responseResult.getResult(); - } - throw new ClientException(responseResult.getError()); - } + private static HttpClient tokenClient = null; + private static HttpClient signClient = null; + private static HttpClient derefClient = null; /** * Get an access token from the token service. A Result of TokenResponse will be returned if the invocation is successfully. @@ -117,43 +91,36 @@ public static Result getTokenResult(TokenRequest tokenRequest) { * @return Result of TokenResponse or error Status. */ public static Result getTokenResult(TokenRequest tokenRequest, String envTag) { - final AtomicReference> reference = new AtomicReference<>(); - final Http2Client client = Http2Client.getInstance(); - final CountDownLatch latch = new CountDownLatch(1); - final ClientConnection connection; - try { - if(tokenRequest.getServerUrl() != null) { - connection = client.connect(new URI(tokenRequest.getServerUrl()), Http2Client.WORKER, Http2Client.SSL, Http2Client.BUFFER_POOL, tokenRequest.isEnableHttp2() ? OptionMap.create(UndertowOptions.ENABLE_HTTP2, true): OptionMap.EMPTY).get(); - } else if(tokenRequest.getServiceId() != null) { - Cluster cluster = SingletonServiceFactory.getBean(Cluster.class); - String url = cluster.serviceToUrl("https", tokenRequest.getServiceId(), envTag, null); - if(logger.isDebugEnabled()) logger.debug("discovered url = " + url); - connection = client.connect(new URI(url), Http2Client.WORKER, Http2Client.SSL, Http2Client.BUFFER_POOL, tokenRequest.isEnableHttp2() ? OptionMap.create(UndertowOptions.ENABLE_HTTP2, true): OptionMap.EMPTY).get(); - } else { - // both server_url and serviceId are empty in the config. - logger.error("Error: both server_url and serviceId are not configured in client.yml for " + tokenRequest.getClass()); - throw new ClientException("both server_url and serviceId are not configured in client.yml for " + tokenRequest.getClass()); + if(tokenClient == null) { + try { + HttpClient.Builder clientBuilder = HttpClient.newBuilder() + .followRedirects(HttpClient.Redirect.NORMAL) + .connectTimeout(Duration.ofMillis(ClientConfig.get().getTimeout())) + .sslContext(Http2Client.createSSLContext()); + if(tokenRequest.getProxyHost() != null) clientBuilder.proxy(ProxySelector.of(new InetSocketAddress(tokenRequest.getProxyHost(), tokenRequest.getProxyPort() == 0 ? 443 : tokenRequest.getProxyPort()))); + if(tokenRequest.isEnableHttp2()) clientBuilder.version(HttpClient.Version.HTTP_2); + tokenClient = clientBuilder.build(); + } catch (IOException e) { + logger.error("Cannot create HttpClient:", e); + return Failure.of(new Status(TLS_TRUSTSTORE_ERROR)); } - } catch (Exception e) { - logger.error("cannot establish connection:", e); - return Failure.of(new Status(ESTABLISH_CONNECTION_ERROR, tokenRequest.getServerUrl() != null? tokenRequest.getServerUrl() : tokenRequest.getServiceId())); } - try { + String serverUrl = tokenRequest.getServerUrl(); + if(serverUrl == null) { + Cluster cluster = SingletonServiceFactory.getBean(Cluster.class); + tokenRequest.setServerUrl(cluster.serviceToUrl("https", tokenRequest.getServiceId(), envTag, null)); + } IClientRequestComposable requestComposer = ClientRequestComposerProvider.getInstance().getComposer(ClientRequestComposerProvider.ClientRequestComposers.CLIENT_CREDENTIAL_REQUEST_COMPOSER); - - connection.getIoThread().execute(new TokenRequestAction(tokenRequest, requestComposer, connection, reference, latch)); - - latch.await(4, TimeUnit.SECONDS); + final HttpRequest request = requestComposer.composeClientRequest(tokenRequest); + CompletableFuture> response = tokenClient.sendAsync(request, HttpResponse.BodyHandlers.ofString()); + String body = response.thenApply(HttpResponse::body).get(); + HttpHeaders headers = response.thenApply(HttpResponse::headers).get(); + return handleResponse(getContentTypeHeaders(headers), body); } catch (Exception e) { - logger.error("IOException: ", e); - return Failure.of(new Status(FAIL_TO_SEND_REQUEST)); - } finally { - IoUtils.safeClose(connection); + logger.error("Exception:", e); + return Failure.of(new Status(ESTABLISH_CONNECTION_ERROR, tokenRequest.getServerUrl())); } - - //if reference.get() is null at this point, mostly likely couldn't get token within latch.await() timeout. - return reference.get() == null ? Failure.of(new Status(GET_TOKEN_TIMEOUT)) : reference.get(); } /** @@ -178,208 +145,99 @@ public static Result getSignResult(SignRequest signRequest) { * @return Result that contains TokenResponse or error status when failed. */ public static Result getSignResult(SignRequest signRequest, String envTag) { - final AtomicReference> reference = new AtomicReference<>(); - final Http2Client client = Http2Client.getInstance(); - final CountDownLatch latch = new CountDownLatch(1); - final ClientConnection connection; - try { - if(signRequest.getServerUrl() != null) { - connection = client.connect(new URI(signRequest.getServerUrl()), Http2Client.WORKER, Http2Client.SSL, Http2Client.BUFFER_POOL, signRequest.isEnableHttp2() ? OptionMap.create(UndertowOptions.ENABLE_HTTP2, true): OptionMap.EMPTY).get(); - } else if(signRequest.getServiceId() != null) { - Cluster cluster = SingletonServiceFactory.getBean(Cluster.class); - String url = cluster.serviceToUrl("https", signRequest.getServiceId(), envTag, null); - connection = client.connect(new URI(url), Http2Client.WORKER, Http2Client.SSL, Http2Client.BUFFER_POOL, signRequest.isEnableHttp2() ? OptionMap.create(UndertowOptions.ENABLE_HTTP2, true): OptionMap.EMPTY).get(); - } else { - // both server_url and serviceId are empty in the config. - logger.error("Error: both server_url and serviceId are not configured in client.yml for " + signRequest.getClass()); - throw new ClientException("both server_url and serviceId are not configured in client.yml for " + signRequest.getClass()); + if(signClient == null) { + try { + HttpClient.Builder clientBuilder = HttpClient.newBuilder() + .followRedirects(HttpClient.Redirect.NORMAL) + .connectTimeout(Duration.ofMillis(ClientConfig.get().getTimeout())) + .sslContext(Http2Client.createSSLContext()); + if(signRequest.getProxyHost() != null) clientBuilder.proxy(ProxySelector.of(new InetSocketAddress(signRequest.getProxyHost(), signRequest.getProxyPort() == 0 ? 443 : signRequest.getProxyPort()))); + if(signRequest.isEnableHttp2()) clientBuilder.version(HttpClient.Version.HTTP_2); + signClient = clientBuilder.build(); + } catch (IOException e) { + logger.error("Cannot create HttpClient:", e); + return Failure.of(new Status(TLS_TRUSTSTORE_ERROR)); } - } catch (Exception e) { - logger.error("cannot establish connection:", e); - return Failure.of(new Status(ESTABLISH_CONNECTION_ERROR, signRequest.getServerUrl() != null ? signRequest.getServerUrl() : signRequest.getServiceId())); } - try { + String serverUrl = signRequest.getServerUrl(); + if(serverUrl == null) { + Cluster cluster = SingletonServiceFactory.getBean(Cluster.class); + signRequest.setServerUrl(cluster.serviceToUrl("https", signRequest.getServiceId(), envTag, null)); + } Map map = new HashMap<>(); map.put("expires", signRequest.getExpires()); map.put("payload", signRequest.getPayload()); String requestBody = Config.getInstance().getMapper().writeValueAsString(map); - connection.getIoThread().execute(() -> { - final ClientRequest request = new ClientRequest().setMethod(Methods.POST).setPath(signRequest.getUri()); - request.getRequestHeaders().put(Headers.HOST, "localhost"); - request.getRequestHeaders().put(Headers.TRANSFER_ENCODING, "chunked"); - request.getRequestHeaders().put(Headers.CONTENT_TYPE, "application/x-www-form-urlencoded"); - request.getRequestHeaders().put(Headers.AUTHORIZATION, getBasicAuthHeader(signRequest.getClientId(), signRequest.getClientSecret())); - connection.sendRequest(request, new ClientCallback() { - @Override - public void completed(ClientExchange result) { - new StringWriteChannelListener(requestBody).setup(result.getRequestChannel()); - result.setResponseListener(new ClientCallback() { - @Override - public void completed(ClientExchange result) { - new StringReadChannelListener(Http2Client.BUFFER_POOL) { - - @Override - protected void stringDone(String string) { - - logger.debug("getToken response = " + string); - reference.set(handleResponse(getContentTypeFromExchange(result), string)); - latch.countDown(); - } - - @Override - protected void error(IOException e) { - logger.error("IOException:", e); - reference.set(Failure.of(new Status(FAIL_TO_SEND_REQUEST))); - latch.countDown(); - } - }.setup(result.getResponseChannel()); - } - - @Override - public void failed(IOException e) { - logger.error("IOException:", e); - reference.set(Failure.of(new Status(FAIL_TO_SEND_REQUEST))); - latch.countDown(); - } - }); - } + HttpRequest.Builder requestBuilder = HttpRequest.newBuilder() + .POST(HttpRequest.BodyPublishers.ofString(requestBody)) + .uri(URI.create(serverUrl + signRequest.getUri())); + if(signRequest.getClientId() != null && signRequest.getClientSecret() != null) { + requestBuilder.setHeader(Headers.AUTHORIZATION_STRING, getBasicAuthHeader(signRequest.getClientId(), signRequest.getClientSecret())); + } + requestBuilder.setHeader(Headers.CONTENT_TYPE_STRING, "application/json"); - @Override - public void failed(IOException e) { - logger.error("IOException:", e); - reference.set(Failure.of(new Status(FAIL_TO_SEND_REQUEST))); - latch.countDown(); - } - }); - }); + HttpRequest request = requestBuilder.build(); - latch.await(signRequest.getTimeout(), TimeUnit.MILLISECONDS); + CompletableFuture> response = signClient.sendAsync(request, HttpResponse.BodyHandlers.ofString()); + String body = response.thenApply(HttpResponse::body).get(); + HttpHeaders headers = response.thenApply(HttpResponse::headers).get(); + return handleResponse(getContentTypeHeaders(headers), body); } catch (Exception e) { - logger.error("IOException: ", e); - return Failure.of(new Status(FAIL_TO_SEND_REQUEST)); - } finally { - IoUtils.safeClose(connection); + logger.error("Exception:", e); + return Failure.of(new Status(ESTABLISH_CONNECTION_ERROR, signRequest.getServerUrl())); } - - //if reference.get() is null at this point, mostly likely couldn't get token within latch.await() timeout. - return reference.get() == null ? Failure.of(new Status(GET_TOKEN_TIMEOUT)) : reference.get(); } /** - * @deprecated As of release 1.5.29, replaced with @link #getTokenFromSamlResult(SAMLBearerRequest tokenRequest) + * Get an access token from the token service based on a SAML token request. A Result of TokenResponse will be returned + * if the invocation is successfully. Otherwise, a Result of Status will be returned. * - * @param tokenRequest Request details for the token - * @return A TokenResponse object on success - * @throws ClientException If any issues + * @param tokenRequest token request constructed from the client.yml token section. + * @return Result of TokenResponse or error Status. */ - @Deprecated - public static TokenResponse getTokenFromSaml(SAMLBearerRequest tokenRequest) throws ClientException { - Result responseResult = getTokenFromSamlResult(tokenRequest); - if (responseResult.isSuccess()) { - return responseResult.getResult(); - } - throw new ClientException(responseResult.getError()); - } - public static Result getTokenFromSamlResult(SAMLBearerRequest tokenRequest) { - final AtomicReference> reference = new AtomicReference<>(); - final Http2Client client = Http2Client.getInstance(); - final CountDownLatch latch = new CountDownLatch(1); - final ClientConnection connection; - try { - connection = client.connect(new URI(tokenRequest.getServerUrl()), Http2Client.WORKER, Http2Client.SSL, Http2Client.BUFFER_POOL, tokenRequest.isEnableHttp2() ? OptionMap.create(UndertowOptions.ENABLE_HTTP2, true): OptionMap.EMPTY).get(); - } catch (Exception e) { - logger.error("cannot establish connection:", e); - return Failure.of(new Status(ESTABLISH_CONNECTION_ERROR)); - } - try { - IClientRequestComposable requestComposer = ClientRequestComposerProvider.getInstance().getComposer(ClientRequestComposerProvider.ClientRequestComposers.SAML_BEARER_REQUEST_COMPOSER); - - connection.getIoThread().execute(new TokenRequestAction(tokenRequest, requestComposer, connection, reference, latch)); - - latch.await(4, TimeUnit.SECONDS); - } catch (Exception e) { - logger.error("IOException: ", e); - return Failure.of(new Status(FAIL_TO_SEND_REQUEST)); - } finally { - IoUtils.safeClose(connection); - } - //if reference.get() is null at this point, mostly likely couldn't get token within latch.await() timeout. - return reference.get() == null ? Failure.of(new Status(GET_TOKEN_TIMEOUT)) : reference.get(); + return getTokenResult(tokenRequest, null); } /** - * This private class is to encapsulate the action to send request, and handling response, - * because of the way of sending request to get token and handle exceptions are the same for both JWT and SAML. - * The only difference is how to compose the request based on TokenRequest model. + * Get an access token from the token service based on a SAML token request. A Result of TokenResponse will be returned + * if the invocation is successfully. Otherwise, a Result of Status will be returned. + * + * @param tokenRequest token request constructed from the client.yml token section. + * @param envTag environment tag for service lookup. + * @return Result of TokenResponse or error Status. */ - private static class TokenRequestAction implements Runnable{ - private ClientConnection connection; - private AtomicReference> reference; - private CountDownLatch latch; - private IClientRequestComposable requestComposer; - private TokenRequest tokenRequest; - - TokenRequestAction(TokenRequest tokenRequest, IClientRequestComposable requestComposer, ClientConnection connection, AtomicReference> reference, CountDownLatch latch){ - this.tokenRequest = tokenRequest; - this.connection = connection; - this.reference = reference; - this.latch = latch; - this.requestComposer = requestComposer; + public static Result getTokenFromSamlResult(SAMLBearerRequest tokenRequest, String envTag) { + if(tokenClient == null) { + try { + HttpClient.Builder clientBuilder = HttpClient.newBuilder() + .followRedirects(HttpClient.Redirect.NORMAL) + .connectTimeout(Duration.ofMillis(ClientConfig.get().getTimeout())) + .sslContext(Http2Client.createSSLContext()); + if(tokenRequest.getProxyHost() != null) clientBuilder.proxy(ProxySelector.of(new InetSocketAddress(tokenRequest.getProxyHost(), tokenRequest.getProxyPort() == 0 ? 443 : tokenRequest.getProxyPort()))); + if(tokenRequest.isEnableHttp2()) clientBuilder.version(HttpClient.Version.HTTP_2); + tokenClient = clientBuilder.build(); + } catch (IOException e) { + logger.error("Cannot create HttpClient:", e); + return Failure.of(new Status(TLS_TRUSTSTORE_ERROR)); + } } - @Override - public void run() { - final ClientRequest request = requestComposer.composeClientRequest(tokenRequest); - String requestBody = requestComposer.composeRequestBody(tokenRequest); - if (logger.isDebugEnabled()) { - logger.debug("The request sent to the oauth server = request header(s): {}, request body: {}", request.getRequestHeaders().toString(), requestBody); + try { + String serverUrl = tokenRequest.getServerUrl(); + if(serverUrl == null) { + Cluster cluster = SingletonServiceFactory.getBean(Cluster.class); + tokenRequest.setServerUrl(cluster.serviceToUrl("https", tokenRequest.getServiceId(), envTag, null)); } - adjustNoChunkedEncoding(request, requestBody); - connection.sendRequest(request, new ClientCallback() { - - @Override - public void completed(ClientExchange result) { - new StringWriteChannelListener(requestBody).setup(result.getRequestChannel()); - result.setResponseListener(new ClientCallback() { - @Override - public void completed(ClientExchange result) { - new StringReadChannelListener(Http2Client.BUFFER_POOL) { - - @Override - protected void stringDone(String string) { - if (logger.isDebugEnabled()) { - logger.debug("getToken response = " + string); - } - reference.set(handleResponse(getContentTypeFromExchange(result), string)); - latch.countDown(); - } - - @Override - protected void error(IOException e) { - logger.error("IOException:", e); - reference.set(Failure.of(new Status(FAIL_TO_SEND_REQUEST))); - latch.countDown(); - } - }.setup(result.getResponseChannel()); - } - - @Override - public void failed(IOException e) { - logger.error("IOException:", e); - reference.set(Failure.of(new Status(FAIL_TO_SEND_REQUEST))); - latch.countDown(); - } - }); - } - - @Override - public void failed(IOException e) { - logger.error("IOException:", e); - reference.set(Failure.of(new Status(FAIL_TO_SEND_REQUEST))); - latch.countDown(); - } - }); + IClientRequestComposable requestComposer = ClientRequestComposerProvider.getInstance().getComposer(ClientRequestComposerProvider.ClientRequestComposers.SAML_BEARER_REQUEST_COMPOSER); + final HttpRequest request = requestComposer.composeClientRequest(tokenRequest); + CompletableFuture> response = tokenClient.sendAsync(request, HttpResponse.BodyHandlers.ofString()); + String body = response.thenApply(HttpResponse::body).get(); + HttpHeaders headers = response.thenApply(HttpResponse::headers).get(); + return handleResponse(getContentTypeHeaders(headers), body); + } catch (Exception e) { + logger.error("IOException: ", e); + return Failure.of(new Status(ESTABLISH_CONNECTION_ERROR, tokenRequest.getServerUrl())); } } @@ -403,44 +261,35 @@ public static String getKey(KeyRequest keyRequest) throws ClientException { * @throws ClientException throw exception if communication with the service fails. */ public static String getKey(KeyRequest keyRequest, String envTag) throws ClientException { - final Http2Client client = Http2Client.getInstance(); - final CountDownLatch latch = new CountDownLatch(1); - final ClientConnection connection; - URI uri = null; - try { - if(keyRequest.getServerUrl() != null) { - uri = new URI(keyRequest.getServerUrl()); - connection = client.connect(uri, Http2Client.WORKER, Http2Client.SSL, Http2Client.BUFFER_POOL, keyRequest.enableHttp2 ? OptionMap.create(UndertowOptions.ENABLE_HTTP2, true): OptionMap.EMPTY).get(); - } else if(keyRequest.getServiceId() != null) { - Cluster cluster = SingletonServiceFactory.getBean(Cluster.class); - String url = cluster.serviceToUrl("https", keyRequest.getServiceId(), envTag, null); - connection = client.connect(new URI(url), Http2Client.WORKER, Http2Client.SSL, Http2Client.BUFFER_POOL, keyRequest.enableHttp2 ? OptionMap.create(UndertowOptions.ENABLE_HTTP2, true): OptionMap.EMPTY).get(); - } else { - // both server_url and serviceId are empty in the config. - logger.error("Error: both server_url and serviceId are not configured in client.yml for " + keyRequest.getClass()); - throw new ClientException("both server_url and serviceId are not configured in client.yml for " + keyRequest.getClass()); - } - } catch (Exception e) { - throw new ClientException(e); + String serverUrl = keyRequest.getServerUrl(); + if(serverUrl == null) { + Cluster cluster = SingletonServiceFactory.getBean(Cluster.class); + serverUrl = cluster.serviceToUrl("https", keyRequest.getServiceId(), envTag, null); } - final AtomicReference reference = new AtomicReference<>(); try { - ClientRequest request = new ClientRequest().setPath(keyRequest.getUri()).setMethod(Methods.GET); - - if (keyRequest.getClientId()!=null) { - request.getRequestHeaders().put(Headers.AUTHORIZATION, getBasicAuthHeader(keyRequest.getClientId(), keyRequest.getClientSecret())); + // The key client is used only during the server startup or jwt key is rotated. Don't cache the keyClient. + HttpClient.Builder clientBuilder = HttpClient.newBuilder() + .followRedirects(HttpClient.Redirect.NORMAL) + .connectTimeout(Duration.ofMillis(ClientConfig.get().getTimeout())) + .sslContext(Http2Client.createSSLContext()); + if(keyRequest.getProxyHost() != null) clientBuilder.proxy(ProxySelector.of(new InetSocketAddress(keyRequest.getProxyHost(), keyRequest.getProxyPort() == 0 ? 443 : keyRequest.getProxyPort()))); + if(keyRequest.isEnableHttp2()) clientBuilder.version(HttpClient.Version.HTTP_2); + HttpClient keyClient = clientBuilder.build(); + + HttpRequest.Builder requestBuilder = HttpRequest.newBuilder() + .GET() + .uri(URI.create(serverUrl + keyRequest.getUri())); + if(keyRequest.getClientId() != null && keyRequest.getClientSecret() != null) { + requestBuilder.setHeader(Headers.AUTHORIZATION_STRING, getBasicAuthHeader(keyRequest.getClientId(), keyRequest.getClientSecret())); } - request.getRequestHeaders().put(Headers.HOST, uri != null ? uri.getHost() : "localhost"); // use the host from the serverUrl to bypass Mcafee local proxy - adjustNoChunkedEncoding(request, ""); - connection.sendRequest(request, client.createClientCallback(reference, latch)); - latch.await(); + HttpRequest request = requestBuilder.build(); + + CompletableFuture> response = + keyClient.sendAsync(request, HttpResponse.BodyHandlers.ofString()); + return response.thenApply(HttpResponse::body).get(ClientConfig.get().getTimeout(), TimeUnit.MILLISECONDS); } catch (Exception e) { - logger.error("Exception: ", e); throw new ClientException(e); - } finally { - IoUtils.safeClose(connection); } - return reference.get().getAttachment(Http2Client.RESPONSE_BODY); } /** @@ -459,43 +308,45 @@ public static String derefToken(DerefRequest derefRequest) throws ClientExceptio * * @param derefRequest a DerefRequest object that is constructed from the client.yml file. * @param envTag an environment tag from the server.yml for cluster service lookup. - * @return String of JWT token + * @return String of JWT token or a status json if there is an error. * @throws ClientException when error occurs. */ public static String derefToken(DerefRequest derefRequest, String envTag) throws ClientException { - final Http2Client client = Http2Client.getInstance(); - final CountDownLatch latch = new CountDownLatch(1); - final ClientConnection connection; - try { - if(derefRequest.getServerUrl() != null) { - connection = client.connect(new URI(derefRequest.getServerUrl()), Http2Client.WORKER, Http2Client.SSL, Http2Client.BUFFER_POOL, derefRequest.isEnableHttp2() ? OptionMap.create(UndertowOptions.ENABLE_HTTP2, true): OptionMap.EMPTY).get(); - } else if(derefRequest.getServiceId() != null) { - Cluster cluster = SingletonServiceFactory.getBean(Cluster.class); - String url = cluster.serviceToUrl("https", derefRequest.getServiceId(), envTag, null); - connection = client.connect(new URI(url), Http2Client.WORKER, Http2Client.SSL, Http2Client.BUFFER_POOL, derefRequest.isEnableHttp2() ? OptionMap.create(UndertowOptions.ENABLE_HTTP2, true): OptionMap.EMPTY).get(); - } else { - // both server_url and serviceId are empty in the config. - logger.error("Error: both server_url and serviceId are not configured in client.yml for " + derefRequest.getClass()); - throw new ClientException("both server_url and serviceId are not configured in client.yml for " + derefRequest.getClass()); + if(derefClient == null) { + try { + HttpClient.Builder clientBuilder = HttpClient.newBuilder() + .followRedirects(HttpClient.Redirect.NORMAL) + .connectTimeout(Duration.ofMillis(ClientConfig.get().getTimeout())) + .sslContext(Http2Client.createSSLContext()); + if(derefRequest.getProxyHost() != null) clientBuilder.proxy(ProxySelector.of(new InetSocketAddress(derefRequest.getProxyHost(), derefRequest.getProxyPort() == 0 ? 443 : derefRequest.getProxyPort()))); + if(derefRequest.isEnableHttp2()) clientBuilder.version(HttpClient.Version.HTTP_2); + derefClient = clientBuilder.build(); + } catch (IOException e) { + logger.error("Cannot create HttpClient:", e); + throw new ClientException(e); } - } catch (Exception e) { - logger.error("Exception: ", e); - throw new ClientException(e); } - final AtomicReference reference = new AtomicReference<>(); try { - ClientRequest request = new ClientRequest().setPath(derefRequest.getUri()).setMethod(Methods.GET); - request.getRequestHeaders().put(Headers.AUTHORIZATION, getBasicAuthHeader(derefRequest.getClientId(), derefRequest.getClientSecret())); - request.getRequestHeaders().put(Headers.HOST, "localhost"); - connection.sendRequest(request, client.createClientCallback(reference, latch)); - latch.await(); + String serverUrl = derefRequest.getServerUrl(); + if(serverUrl == null) { + Cluster cluster = SingletonServiceFactory.getBean(Cluster.class); + serverUrl = cluster.serviceToUrl("https", derefRequest.getServiceId(), envTag, null); + } + + HttpRequest.Builder requestBuilder = HttpRequest.newBuilder() + .GET() + .uri(URI.create(serverUrl + derefRequest.getUri())); + if(derefRequest.getClientId() != null && derefRequest.getClientSecret() != null) { + requestBuilder.setHeader(Headers.AUTHORIZATION_STRING, getBasicAuthHeader(derefRequest.getClientId(), derefRequest.getClientSecret())); + } + HttpRequest request = requestBuilder.build(); + + CompletableFuture> response = derefClient.sendAsync(request, HttpResponse.BodyHandlers.ofString()); + return response.thenApply(HttpResponse::body).get(ClientConfig.get().getTimeout(), TimeUnit.MILLISECONDS); } catch (Exception e) { - logger.error("Exception: ", e); + logger.error("Exception:", e); throw new ClientException(e); - } finally { - IoUtils.safeClose(connection); } - return reference.get().getAttachment(Http2Client.RESPONSE_BODY); } public static String getBasicAuthHeader(String clientId, String clientSecret) { @@ -522,11 +373,11 @@ public static String getEncodedString(TokenRequest request) throws UnsupportedEn params.put(CODE, ((AuthorizationCodeRequest)request).getAuthCode()); // The redirectUri can be null so that OAuth 2.0 provider will use the redirectUri defined in the client registration if(((AuthorizationCodeRequest)request).getRedirectUri() != null) { - params.put(REDIRECT_URI, ((AuthorizationCodeRequest)request).getRedirectUri()); + params.put(ClientConfig.REDIRECT_URI, ((AuthorizationCodeRequest)request).getRedirectUri()); } String csrf = request.getCsrf(); if(csrf != null) { - params.put(CSRF, csrf); + params.put(ClientConfig.CSRF, csrf); } } if(ClientConfig.CLIENT_AUTHENTICATED_USER.equals(request.getGrantType())) { @@ -535,18 +386,18 @@ public static String getEncodedString(TokenRequest request) throws UnsupportedEn params.put(ROLES, ((ClientAuthenticatedUserRequest)request).getRoles()); // The redirectUri can be null so that OAuth 2.0 provider will use the redirectUri defined in the client registration if(((ClientAuthenticatedUserRequest)request).getRedirectUri() != null) { - params.put(REDIRECT_URI, ((ClientAuthenticatedUserRequest)request).getRedirectUri()); + params.put(ClientConfig.REDIRECT_URI, ((ClientAuthenticatedUserRequest)request).getRedirectUri()); } String csrf = request.getCsrf(); if(csrf != null) { - params.put(CSRF, csrf); + params.put(ClientConfig.CSRF, csrf); } } if(ClientConfig.REFRESH_TOKEN.equals(request.getGrantType())) { - params.put(REFRESH_TOKEN, ((RefreshTokenRequest)request).getRefreshToken()); + params.put(ClientConfig.REFRESH_TOKEN, ((RefreshTokenRequest)request).getRefreshToken()); String csrf = request.getCsrf(); if(csrf != null) { - params.put(CSRF, csrf); + params.put(ClientConfig.CSRF, csrf); } } if(request.getScope() != null) { @@ -717,9 +568,9 @@ private static void setScope(TokenRequest tokenRequest, Jwt jwt) { } } - public static ContentType getContentTypeFromExchange(ClientExchange exchange) { - HeaderValues headerValues = exchange.getResponse().getResponseHeaders().get(Headers.CONTENT_TYPE); - return headerValues == null ? ContentType.ANY_TYPE : ContentType.toContentType(headerValues.getFirst()); + public static ContentType getContentTypeHeaders(HttpHeaders headers) { + Optional contentType = headers.firstValue(Headers.CONTENT_TYPE_STRING); + return contentType.isEmpty() ? ContentType.ANY_TYPE : ContentType.toContentType(contentType.get()); } private static String escapeBasedOnType(ContentType contentType, String responseBody) { @@ -776,25 +627,4 @@ private static String escapeXml (String nonEscapedXmlStr) { return escapedXML.toString(); } - /** - * this method is to support sending a server which doesn't support chunked transfer encoding. - * @param request ClientRequest - * @param requestBody String - */ - public static void adjustNoChunkedEncoding(ClientRequest request, String requestBody) { - String fixedLengthString = request.getRequestHeaders().getFirst(Headers.CONTENT_LENGTH); - String transferEncodingString = request.getRequestHeaders().getLast(Headers.TRANSFER_ENCODING); - if(transferEncodingString != null) { - request.getRequestHeaders().remove(Headers.TRANSFER_ENCODING); - } - //if already specify a content-length, should use what they provided - if(fixedLengthString != null && Long.parseLong(fixedLengthString) > 0) { - return; - } - if(!StringUtils.isEmpty(requestBody)) { - long contentLength = requestBody.getBytes(UTF_8).length; - request.getRequestHeaders().put(Headers.CONTENT_LENGTH, contentLength); - } - - } } diff --git a/client/src/main/java/com/networknt/client/oauth/RefreshTokenRequest.java b/client/src/main/java/com/networknt/client/oauth/RefreshTokenRequest.java index 46a959764e..b4e9007920 100644 --- a/client/src/main/java/com/networknt/client/oauth/RefreshTokenRequest.java +++ b/client/src/main/java/com/networknt/client/oauth/RefreshTokenRequest.java @@ -32,6 +32,9 @@ public RefreshTokenRequest() { Map tokenConfig = ClientConfig.get().getTokenConfig(); if(tokenConfig != null) { setServerUrl((String)tokenConfig.get(ClientConfig.SERVER_URL)); + setProxyHost((String)tokenConfig.get(ClientConfig.PROXY_HOST)); + int port = tokenConfig.get(ClientConfig.PROXY_PORT) == null ? 443 : (Integer)tokenConfig.get(ClientConfig.PROXY_PORT); + setProxyPort(port); setServiceId((String)tokenConfig.get(ClientConfig.SERVICE_ID)); Object object = tokenConfig.get(ClientConfig.ENABLE_HTTP2); setEnableHttp2(object != null && (Boolean) object); diff --git a/client/src/main/java/com/networknt/client/oauth/SAMLBearerRequest.java b/client/src/main/java/com/networknt/client/oauth/SAMLBearerRequest.java index 3120c434bf..7654286108 100644 --- a/client/src/main/java/com/networknt/client/oauth/SAMLBearerRequest.java +++ b/client/src/main/java/com/networknt/client/oauth/SAMLBearerRequest.java @@ -52,7 +52,10 @@ public SAMLBearerRequest(String samlAssertion, String jwtClientAssertion) { try { Map tokenConfig = ClientConfig.get().getTokenConfig(); - setServerUrl((String) tokenConfig.get(ClientConfig.SERVER_URL)); + setServerUrl((String)tokenConfig.get(ClientConfig.SERVER_URL)); + setProxyHost((String)tokenConfig.get(ClientConfig.PROXY_HOST)); + int port = tokenConfig.get(ClientConfig.PROXY_PORT) == null ? 443 : (Integer)tokenConfig.get(ClientConfig.PROXY_PORT); + setProxyPort(port); Object object = tokenConfig.get(ClientConfig.ENABLE_HTTP2); setEnableHttp2(object != null && (Boolean) object); Map ccConfig = (Map) tokenConfig.get(ClientConfig.CLIENT_CREDENTIALS); diff --git a/client/src/main/java/com/networknt/client/oauth/SignKeyRequest.java b/client/src/main/java/com/networknt/client/oauth/SignKeyRequest.java index 0e984a602d..a56000139f 100644 --- a/client/src/main/java/com/networknt/client/oauth/SignKeyRequest.java +++ b/client/src/main/java/com/networknt/client/oauth/SignKeyRequest.java @@ -45,6 +45,9 @@ public SignKeyRequest(String kid) { Map keyConfig = (Map)signConfig.get(ClientConfig.KEY); if(keyConfig != null) { setServerUrl((String)keyConfig.get(ClientConfig.SERVER_URL)); + setProxyHost((String)keyConfig.get(ClientConfig.PROXY_HOST)); + int port = keyConfig.get(ClientConfig.PROXY_PORT) == null ? 443 : (Integer)keyConfig.get(ClientConfig.PROXY_PORT); + setProxyPort(port); setServiceId((String)keyConfig.get(ClientConfig.SERVICE_ID)); Object object = keyConfig.get(ClientConfig.ENABLE_HTTP2); setEnableHttp2(object != null && (Boolean) object); diff --git a/client/src/main/java/com/networknt/client/oauth/SignRequest.java b/client/src/main/java/com/networknt/client/oauth/SignRequest.java index f3c26cdb27..d8724d6e18 100644 --- a/client/src/main/java/com/networknt/client/oauth/SignRequest.java +++ b/client/src/main/java/com/networknt/client/oauth/SignRequest.java @@ -30,61 +30,9 @@ * */ public class SignRequest { - /** - * @deprecated will be moved to {@link ClientConfig#OAUTH} - */ - @Deprecated - public static String OAUTH = "oauth"; - - /** - * @deprecated will be moved to {@link ClientConfig#SIGN} - */ - @Deprecated - public static String SIGN = "sign"; - - /** - * @deprecated will be moved to {@link ClientConfig#SERVER_URL} - */ - @Deprecated - public static String SERVER_URL = "server_url"; - - /** - * @deprecated will be moved to {@link ClientConfig#SERVICE_ID} - */ - @Deprecated - public static String SERVICE_ID = "serviceId"; - - /** - * @deprecated will be moved to {@link ClientConfig#URI} - */ - @Deprecated - public static String URI = "uri"; - - /** - * @deprecated will be moved to {@link ClientConfig#ENABLE_HTTP2} - */ - @Deprecated - public static String ENABLE_HTTP2 = "enableHttp2"; - - /** - * @deprecated will be moved to {@link ClientConfig#TIMEOUT} - */ - @Deprecated - public static String TIMEOUT = "timeout"; - - /** - * @deprecated will be moved to {@link ClientConfig#CLIENT_ID} - */ - @Deprecated - public static String CLIENT_ID = "client_id"; - - /** - * @deprecated will be moved to {@link ClientConfig#CLIENT_SECRET} - */ - @Deprecated - public static String CLIENT_SECRET = "client_secret"; - private String serverUrl; + private String proxyHost; + private int proxyPort; private String serviceId; private boolean enableHttp2; private String uri; @@ -98,6 +46,9 @@ public SignRequest() { Map signConfig = ClientConfig.get().getSignConfig(); if(signConfig != null) { setServerUrl((String)signConfig.get(ClientConfig.SERVER_URL)); + setProxyHost((String)signConfig.get(ClientConfig.PROXY_HOST)); + int port = signConfig.get(ClientConfig.PROXY_PORT) == null ? 443 : (Integer)signConfig.get(ClientConfig.PROXY_PORT); + setProxyPort(port); setServiceId((String)signConfig.get(ClientConfig.SERVICE_ID)); setUri((String)signConfig.get(ClientConfig.URI)); timeout = (Integer) signConfig.get(ClientConfig.TIMEOUT); @@ -179,4 +130,20 @@ public Map getPayload() { public void setPayload(Map payload) { this.payload = payload; } + + public String getProxyHost() { + return proxyHost; + } + + public void setProxyHost(String proxyHost) { + this.proxyHost = proxyHost; + } + + public int getProxyPort() { + return proxyPort; + } + + public void setProxyPort(int proxyPort) { + this.proxyPort = proxyPort; + } } diff --git a/client/src/main/java/com/networknt/client/oauth/TokenKeyRequest.java b/client/src/main/java/com/networknt/client/oauth/TokenKeyRequest.java index f8d11b2c5e..c17d81b2cd 100644 --- a/client/src/main/java/com/networknt/client/oauth/TokenKeyRequest.java +++ b/client/src/main/java/com/networknt/client/oauth/TokenKeyRequest.java @@ -83,6 +83,9 @@ public TokenKeyRequest(String kid, boolean jwk) { private void setKeyOptions(Map keyConfig) { setServerUrl((String)keyConfig.get(ClientConfig.SERVER_URL)); + setProxyHost((String)keyConfig.get(ClientConfig.PROXY_HOST)); + int port = keyConfig.get(ClientConfig.PROXY_PORT) == null ? 443 : (Integer)keyConfig.get(ClientConfig.PROXY_PORT); + setProxyPort(port); setServiceId((String)keyConfig.get(ClientConfig.SERVICE_ID)); Object object = keyConfig.get(ClientConfig.ENABLE_HTTP2); setEnableHttp2(object != null && (Boolean) object); diff --git a/client/src/main/java/com/networknt/client/oauth/TokenRequest.java b/client/src/main/java/com/networknt/client/oauth/TokenRequest.java index c4c39aa54c..69e11ad0ba 100644 --- a/client/src/main/java/com/networknt/client/oauth/TokenRequest.java +++ b/client/src/main/java/com/networknt/client/oauth/TokenRequest.java @@ -16,101 +16,16 @@ package com.networknt.client.oauth; -import com.networknt.client.ClientConfig; - import java.util.List; /** * Created by steve on 02/09/16. */ public class TokenRequest { - - /** - * @deprecated will be move to {@link ClientConfig#OAUTH} - */ - @Deprecated - public static String OAUTH = "oauth"; - - /** - * @deprecated will be move to {@link ClientConfig#TOKEN} - */ - @Deprecated - public static String TOKEN = "token"; - - /** - * @deprecated will be move to {@link ClientConfig#SERVER_URL} - */ - @Deprecated - public static String SERVER_URL = "server_url"; - - /** - * @deprecated will be move to {@link ClientConfig#SERVICE_ID} - */ - @Deprecated - public static String SERVICE_ID = "serviceId"; - - /** - * @deprecated will be move to {@link ClientConfig#ENABLE_HTTP2} - */ - @Deprecated - public static String ENABLE_HTTP2 = "enableHttp2"; - - /** - * @deprecated will be move to {@link ClientConfig#AUTHORIZATION_CODE} - */ - @Deprecated - public static String AUTHORIZATION_CODE = "authorization_code"; - - /** - * @deprecated will be move to {@link ClientConfig#CLIENT_CREDENTIALS} - */ - @Deprecated - public static String CLIENT_CREDENTIALS = "client_credentials"; - - /** - * @deprecated will be move to {@link ClientConfig#SAML_BEARER} - */ - @Deprecated - public static String SAML_BEARER = "saml_bearer"; - - /** - * @deprecated will be move to {@link ClientConfig#REFRESH_TOKEN} - */ - @Deprecated - public static String REFRESH_TOKEN = "refresh_token"; - - /** - * @deprecated will be move to {@link ClientConfig#URI} - */ - @Deprecated - public static String URI = "uri"; - - /** - * @deprecated will be move to {@link ClientConfig#CLIENT_ID} - */ - @Deprecated - public static String CLIENT_ID = "client_id"; - - /** - * @deprecated will be move to {@link ClientConfig#REDIRECT_URI} - */ - @Deprecated - public static String REDIRECT_URI = "redirect_uri"; - - /** - * @deprecated will be move to {@link ClientConfig#SCOPE} - */ - @Deprecated - public static String SCOPE = "scope"; - - /** - * @deprecated will be move to {@link ClientConfig#CSRF} - */ - @Deprecated - public static String CSRF = "csrf"; - private String grantType; private String serverUrl; + private String proxyHost; + private int proxyPort; private String serviceId; private boolean enableHttp2; private String uri; @@ -189,5 +104,19 @@ public void setServiceId(String serviceId) { public void setCsrf(String csrf) { this.csrf = csrf; } + public String getProxyHost() { + return proxyHost; + } + + public void setProxyHost(String proxyHost) { + this.proxyHost = proxyHost; + } + public int getProxyPort() { + return proxyPort; + } + + public void setProxyPort(int proxyPort) { + this.proxyPort = proxyPort; + } } diff --git a/client/src/main/resources/config/client.yml b/client/src/main/resources/config/client.yml index 5b45194dfb..08cdbe81ca 100644 --- a/client/src/main/resources/config/client.yml +++ b/client/src/main/resources/config/client.yml @@ -43,6 +43,15 @@ oauth: # token server url. The default port number for token service is 6882. If this is set, # it will take high priority than serviceId for the direct connection # server_url: ${client.tokenServerUrl:https://localhost:6882} + # For users who leverage SaaS OAuth 2.0 provider from lightapi.net or others in the public cloud + # and has an internal proxy server to access code, token and key services of OAuth 2.0, set up the + # proxyHost here for the HTTPS traffic. This option is only working with server_url and serviceId + # below should be commented out. OAuth 2.0 services cannot be discovered if a proxy server is used. + # proxyHost: ${client.tokenProxyHost:proxy.lightapi.net} + # We only support HTTPS traffic for the proxy and the default port is 443. If your proxy server has + # a different port, please specify it here. If proxyHost is available and proxyPort is missing, then + # the default value 443 is going to be used for the HTTP connection. + # proxyPort: ${client.tokenProxyPort:3128} # token service unique id for OAuth 2.0 provider. If server_url is not set above, # a service discovery action will be taken to find an instance of token service. serviceId: ${client.tokenServiceId:com.networknt.oauth2-token-1.0.0} @@ -93,6 +102,15 @@ oauth: # key distribution server url for token verification. It will be used if it is configured. # If it is not set, a service lookup will be taken with serviceId to find an instance. # server_url: ${client.tokenKeyServerUrl:https://localhost:6886} + # For users who leverage SaaS OAuth 2.0 provider from lightapi.net or others in the public cloud + # and has an internal proxy server to access code, token and key services of OAuth 2.0, set up the + # proxyHost here for the HTTPS traffic. This option is only working with server_url and serviceId + # below should be commented out. OAuth 2.0 services cannot be discovered if a proxy server is used. + # proxyHost: ${client.tokenKeyProxyHost:proxy.lightapi.net} + # We only support HTTPS traffic for the proxy and the default port is 443. If your proxy server has + # a different port, please specify it here. If proxyHost is available and proxyPort is missing, then + # the default value 443 is going to be used for the HTTP connection. + # proxyPort: ${client.tokenKeyProxyPort:3128} # key serviceId for key distribution service, it will be used if above server_url is not configured. serviceId: ${client.tokenKeyServiceId:com.networknt.oauth2-key-1.0.0} # the path for the key distribution endpoint @@ -108,6 +126,15 @@ oauth: # token server url. The default port number for token service is 6882. If this url exists, it will be used. # if it is not set, then a service lookup against serviceId will be taken to discover an instance. # server_url: ${client.signServerUrl:https://localhost:6882} + # For users who leverage SaaS OAuth 2.0 provider from lightapi.net or others in the public cloud + # and has an internal proxy server to access code, token and key services of OAuth 2.0, set up the + # proxyHost here for the HTTPS traffic. This option is only working with server_url and serviceId + # below should be commented out. OAuth 2.0 services cannot be discovered if a proxy server is used. + # proxyHost: ${client.signProxyHost:proxy.lightapi.net} + # We only support HTTPS traffic for the proxy and the default port is 443. If your proxy server has + # a different port, please specify it here. If proxyHost is available and proxyPort is missing, then + # the default value 443 is going to be used for the HTTP connection. + # proxyPort: ${client.signProxyPort:3128} # token serviceId. If server_url doesn't exist, the serviceId will be used to lookup the token service. serviceId: ${client.signServiceId:com.networknt.oauth2-token-1.0.0} # signing endpoint for the sign request @@ -125,6 +152,15 @@ oauth: # key distribution server url. It will be used to establish connection if it exists. # if it is not set, then a service lookup against serviceId will be taken to discover an instance. # server_url: ${client.signKeyServerUrl:https://localhost:6886} + # For users who leverage SaaS OAuth 2.0 provider from lightapi.net or others in the public cloud + # and has an internal proxy server to access code, token and key services of OAuth 2.0, set up the + # proxyHost here for the HTTPS traffic. This option is only working with server_url and serviceId + # below should be commented out. OAuth 2.0 services cannot be discovered if a proxy server is used. + # proxyHost: ${client.signKeyProxyHost:proxy.lightapi.net} + # We only support HTTPS traffic for the proxy and the default port is 443. If your proxy server has + # a different port, please specify it here. If proxyHost is available and proxyPort is missing, then + # the default value 443 is going to be used for the HTTP connection. + # proxyPort: ${client.signKeyProxyPort:3128} # the unique service id for key distribution service, it will be used to lookup key service if above url doesn't exist. serviceId: ${client.signKeyServiceId:com.networknt.oauth2-key-1.0.0} # the path for the key distribution endpoint @@ -139,6 +175,15 @@ oauth: deref: # Token service server url, this might be different than the above token server url. The static url will be used if it is configured. # server_url: ${client.derefServerUrl:https://localhost:6882} + # For users who leverage SaaS OAuth 2.0 provider in the public cloud and has an internal + # proxy server to access code, token and key services of OAuth 2.0, set up the proxyHost + # here for the HTTPS traffic. This option is only working with server_url and serviceId + # below should be commented out. OAuth 2.0 services cannot be discovered if a proxy is used. + # proxyHost: ${client.derefProxyHost:proxy.lightapi.net} + # We only support HTTPS traffic for the proxy and the default port is 443. If your proxy server has + # a different port, please specify it here. If proxyHost is available and proxyPort is missing, then + # the default value 443 is going to be used for the HTTP connection. + # proxyPort: ${client.derefProxyPort:3128} # token service unique id for OAuth 2.0 provider. Need for service lookup/discovery. It will be used if above server_url is not configured. serviceId: ${client.derefServiceId:com.networknt.oauth2-token-1.0.0} # set to true if the oauth2 provider supports HTTP/2 diff --git a/client/src/test/java/com/networknt/client/oauth/OauthHelperTest.java b/client/src/test/java/com/networknt/client/oauth/OauthHelperTest.java index 23ce89ce80..9890a5ec1d 100644 --- a/client/src/test/java/com/networknt/client/oauth/OauthHelperTest.java +++ b/client/src/test/java/com/networknt/client/oauth/OauthHelperTest.java @@ -16,6 +16,7 @@ package com.networknt.client.oauth; +import com.networknt.client.ClientConfig; import com.networknt.client.Http2Client; import com.networknt.config.Config; import com.networknt.monad.Result; @@ -90,13 +91,13 @@ public static void setUp() { public static void tearDown() throws Exception { if(server != null) { try { - Thread.sleep(100); + Thread.sleep(1000); } catch (InterruptedException ignored) { } server.stop(); System.out.println("The server is stopped."); try { - Thread.sleep(100); + Thread.sleep(1000); } catch (InterruptedException ignored) { } } @@ -214,7 +215,7 @@ public void testGetTokenResult() throws Exception { AuthorizationCodeRequest tokenRequest = new AuthorizationCodeRequest(); tokenRequest.setClientId("test_client"); tokenRequest.setClientSecret("test_secret"); - tokenRequest.setGrantType(TokenRequest.AUTHORIZATION_CODE); + tokenRequest.setGrantType(ClientConfig.AUTHORIZATION_CODE); List list = new ArrayList<>(); list.add("test.r"); list.add("test.w"); @@ -237,7 +238,7 @@ public void testGetToken() throws Exception { AuthorizationCodeRequest tokenRequest = new AuthorizationCodeRequest(); tokenRequest.setClientId("test_client"); tokenRequest.setClientSecret("test_secret"); - tokenRequest.setGrantType(TokenRequest.AUTHORIZATION_CODE); + tokenRequest.setGrantType(ClientConfig.AUTHORIZATION_CODE); List list = new ArrayList<>(); list.add("test.r"); list.add("test.w"); @@ -248,9 +249,10 @@ public void testGetToken() throws Exception { tokenRequest.setRedirectUri("https://localhost:8443/authorize"); tokenRequest.setAuthCode("test_code"); - TokenResponse tokenResponse = OauthHelper.getToken(tokenRequest); - Assert.assertNotNull(tokenResponse); - System.out.println("tokenResponse = " + tokenResponse); + Result result = OauthHelper.getTokenResult(tokenRequest); + Assert.assertTrue(result.isSuccess()); + Assert.assertNotNull(result.getResult()); + System.out.println("tokenResponse = " + result.getResult()); } @Test diff --git a/deref-token/src/test/resources/config/client.keystore b/deref-token/src/test/resources/config/client.keystore new file mode 100644 index 0000000000..c593b3758e Binary files /dev/null and b/deref-token/src/test/resources/config/client.keystore differ diff --git a/deref-token/src/test/resources/config/client.truststore b/deref-token/src/test/resources/config/client.truststore new file mode 100644 index 0000000000..ded19d0cdf Binary files /dev/null and b/deref-token/src/test/resources/config/client.truststore differ diff --git a/deref-token/src/test/resources/config/client.yml b/deref-token/src/test/resources/config/client.yml index 585fbad622..198d088549 100644 --- a/deref-token/src/test/resources/config/client.yml +++ b/deref-token/src/test/resources/config/client.yml @@ -1,15 +1,30 @@ --- tls: - # if the server is using self-signed certificate, this need to be false. - verifyHostname: false + # if the server is using self-signed certificate, this need to be false. If true, you have to use CA signed certificate + # or load truststore that contains the self-signed cretificate. + verifyHostname: ${client.verifyHostname:false} + # The default trustedNames group used to created default SSL context. This is used to create Http2Client.SSL if set. + defaultGroupKey: ${client.defaultGroupKey:trustedNames.local} + # trusted hostnames, service names, service Ids, and so on. + # Note: localhost and 127.0.0.1 are not trustable hostname/ip in general. So, these values should not be used as trusted names in production. + trustedNames: + local: localhost + negativeTest: invalidhost + empty: # trust store contains certifictes that server needs. Enable if tls is used. - loadTrustStore: true + loadTrustStore: ${client.loadTrustStore:true} # trust store location can be specified here or system properties javax.net.ssl.trustStore and password javax.net.ssl.trustStorePassword - trustStore: client.truststore + trustStore: ${client.trustStore:client.truststore} + # trust store password + trustStorePass: ${client.trustStorePass:password} # key store contains client key and it should be loaded if two-way ssl is uesed. - loadKeyStore: true + loadKeyStore: ${client.loadKeyStore:false} # key store location - keyStore: client.keystore + keyStore: ${client.keyStore:client.keystore} + # key store password + keyStorePass: ${client.keyStorePass:password} + # private key password + keyPass: ${client.keyPass:password} oauth: token: tokenRenewBeforeExpired: 4000 diff --git a/status/src/main/resources/config/status.yml b/status/src/main/resources/config/status.yml index 76a891d578..9b43dce357 100644 --- a/status/src/main/resources/config/status.yml +++ b/status/src/main/resources/config/status.yml @@ -397,6 +397,11 @@ ERR10054: code: ERR10054 message: GET_TOKEN_TIMEOUT description: Cannot get valid token, probably due to a timeout. +ERR10055: + statusCode: 400 + code: ERR10055 + message: TLS_TRUSTSTORE_ERROR + description: Cannot load client.truststore to create HttpClient. # 11000-11500 swagger-validator errors ERR11000: