Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add https support #47

Merged
merged 16 commits into from
Jun 11, 2020
4 changes: 2 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

<groupId>com.baidu.hugegraph</groupId>
<artifactId>hugegraph-common</artifactId>
<version>1.7.5</version>
<version>1.7.6</version>

<name>hugegraph-common</name>
<url>https://github.com/hugegraph/hugegraph-common</url>
Expand Down Expand Up @@ -260,7 +260,7 @@
<manifestEntries>
<!-- Must be on one line, otherwise the automatic
upgrade script cannot replace the version number -->
<Implementation-Version>1.7.5.0</Implementation-Version>
<Implementation-Version>1.7.6.0</Implementation-Version>
</manifestEntries>
</archive>
</configuration>
Expand Down
108 changes: 106 additions & 2 deletions src/main/java/com/baidu/hugegraph/rest/RestClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,23 @@

import static org.glassfish.jersey.apache.connector.ApacheClientProperties.CONNECTION_MANAGER;

import java.net.URI;
import java.security.KeyManagementException;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Collection;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.Entity;
Expand All @@ -40,6 +51,7 @@
import org.apache.commons.lang3.tuple.Pair;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.pool.PoolStats;
import org.glassfish.jersey.SslConfigurator;
import org.glassfish.jersey.apache.connector.ApacheConnectorProvider;
import org.glassfish.jersey.client.ClientConfig;
import org.glassfish.jersey.client.ClientProperties;
Expand Down Expand Up @@ -90,9 +102,70 @@ public RestClient(String url, String user, String password, int timeout,
.config(maxTotal, maxPerRoute)
.build());
}


public RestClient(String url, String user, String password, int timeout,
int maxTotal, int maxPerRoute, String protocol,
String trustStoreFile, String trustStorePassword) {
this(url, new ConfigBuilder().config(timeout)
.config(user, password)
.config(maxTotal, maxPerRoute)
.config(protocol, trustStoreFile,
trustStorePassword)
.build());
}

private TrustManager[] createNoCheckTrustManager() {
return new TrustManager[]{new NoCheckTrustManager()};
}

private static class NoCheckTrustManager implements X509TrustManager {

@Override
shzcore marked this conversation as resolved.
Show resolved Hide resolved
public void checkClientTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
}

@Override
public void checkServerTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
}

@Override
public X509Certificate[] getAcceptedIssuers() {
return null;
}
}

private Client wrapTrustConfig(String url, ClientConfig config) {

SslConfigurator sslConfig = SslConfigurator.newInstance();
String trustStoreFile = config.getProperty("trustStoreFile").toString();
String trustStorePassword = config.getProperty("trustStorePassword").toString();
sslConfig.trustStoreFile(trustStoreFile)
.trustStorePassword(trustStorePassword);
sslConfig.securityProtocol("SSL");
SSLContext sc = sslConfig.createSSLContext();
TrustManager[] trustAllCerts = createNoCheckTrustManager();
try {
sc.init(null, trustAllCerts, new SecureRandom());
} catch (KeyManagementException e) {
throw new ClientException("Failed to init security key management", e);
}
return ClientBuilder.newBuilder()
.hostnameVerifier(new HostNameVerifier(url))
.sslContext(sc)
.build();
}

public RestClient(String url, ClientConfig config) {
this.client = ClientBuilder.newClient(config);
Client client = null;
Object protocol = config.getProperty("protocol");
if (protocol != null && protocol.equals("https")) {
client = wrapTrustConfig(url, config);
} else {
client = ClientBuilder.newClient(config);
}
this.client = client;
this.client.register(GZipEncoder.class);
this.target = this.client.target(url);
this.pool = (PoolingHttpClientConnectionManager)
Expand Down Expand Up @@ -284,6 +357,29 @@ private static String encode(String raw) {
return UriComponent.encode(raw, UriComponent.Type.PATH_SEGMENT);
}

public static class HostNameVerifier implements HostnameVerifier {

private final String url;

public HostNameVerifier(String url) {
if (!url.startsWith("http://") && !url.startsWith("https://")) {
url = "http://" + url;
}
url = URI.create(url).getHost();
this.url = url;
}

@Override
public boolean verify(String hostname, SSLSession session) {
if (!this.url.isEmpty() && this.url.endsWith(hostname)) {
return true;
} else {
HostnameVerifier verifier = HttpsURLConnection.getDefaultHostnameVerifier();
return verifier.verify(hostname, session);
}
}
}

protected abstract void checkStatus(Response response,
Response.Status... statuses);

Expand Down Expand Up @@ -336,6 +432,14 @@ public ConfigBuilder config(int maxTotal, int maxPerRoute) {
return this;
}

public ConfigBuilder config(String protocol, String trustStoreFile,
String trustStorePassword) {
this.config.property("protocol", protocol);
this.config.property("trustStoreFile", trustStoreFile);
this.config.property("trustStorePassword", trustStorePassword);
return this;
}

public ClientConfig build() {
return this.config;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,5 @@ public class CommonVersion {

// The second parameter of Version.of() is for all-in-one JAR
public static final Version VERSION = Version.of(CommonVersion.class,
"1.7.5");
"1.7.6");
}
54 changes: 54 additions & 0 deletions src/test/java/com/baidu/hugegraph/unit/rest/RestClientTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,18 @@

package com.baidu.hugegraph.unit.rest;

import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.BiFunction;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSessionContext;
import javax.ws.rs.core.MultivaluedHashMap;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
Expand Down Expand Up @@ -80,6 +85,17 @@ public RestClientImpl(String url, String user, String password,
this.content = "";
}

public RestClientImpl(String url, String user, String password,
int timeout, int maxTotal, int maxPerRoute,
String protocol, String trustStoreFile,
String trustStorePassword, int status) {
super(url, user, password, timeout, maxTotal, maxPerRoute, protocol,
trustStoreFile, trustStorePassword);
this.status = status;
this.headers = ImmutableMultivaluedMap.empty();
this.content = "";
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just add a test case: testHttpsGet:

    public void testHttpsGet() {
        RestClient client = new RestClientImpl(...);
        RestResult restResult = client.get("path", "id1");
        Assert.assertEquals(200, restResult.status());
    }

public RestClientImpl(String url, int timeout, int status) {
this(url, timeout, status, ImmutableMultivaluedMap.empty(), "");
}
Expand Down Expand Up @@ -221,6 +237,44 @@ public void testPostWithAllParams() {
Assert.assertEquals(200, restResult.status());
}

@Test
public void testPostHttpsWithAllParams() {
String trustStoreFile = "src/test/resources/cacerts.jks";
String trustStorePassword = "changeit";
RestClient client = new RestClientImpl("/test", "user", "", 1000,
10, 5, "https", trustStoreFile,
trustStorePassword, 200);
RestResult restResult = client.post("path", "body");
Assert.assertEquals(200, restResult.status());
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add test for HostNameVerifier

@Test
public void testHostNameVerifier() {
BiFunction<String, String, Boolean> verifer = (url, hostname) -> {
RestClient.HostNameVerifier verifier;
SSLSession session;
try {
SSLSessionContext sc = SSLContext.getDefault()
.getClientSessionContext();
session = sc.getSession(new byte[]{11});
verifier = new RestClient.HostNameVerifier(url);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
return verifier.verify(hostname, session);
};

Assert.assertTrue(verifer.apply("http://baidu.com", "baidu.com"));
Assert.assertTrue(verifer.apply("http://test1.baidu.com", "baidu.com"));
Assert.assertTrue(verifer.apply("http://test2.baidu.com", "baidu.com"));
Assert.assertFalse(verifer.apply("http://baidu2.com", "baidu.com"));
Assert.assertTrue(verifer.apply("http://baidu.com", ""));
Assert.assertTrue(verifer.apply("baidu.com", "baidu.com"));
Assert.assertTrue(verifer.apply("http://baidu.com/test/abc", "baidu.com"));
Assert.assertTrue(verifer.apply("baidu.com/test/abc", "baidu.com"));
Assert.assertFalse(verifer.apply("baidu.com.sina.com", "baidu.com"));
}

javeme marked this conversation as resolved.
Show resolved Hide resolved
@Test
public void testPostWithHeaderAndContent() {
MultivaluedMap<String, Object> headers = new MultivaluedHashMap<>();
Expand Down
Binary file added src/test/resources/cacerts.jks
Binary file not shown.