假设我们需要信任自签名SSL证书。例如,我们使用https://self-signed.badssl.com/。
由于签名者不是“适当的”权限,Java不信任它并拒绝连接到该服务器。但是,在
之后$ cd $JAVA_HOME/jre/lib/security
$ keytool -import -trustcacerts -alias ... -file ... -keystore cacerts
并重新启动应用程序,以下代码有效:
new URL ("https://self-signed.badssl.com/").openConnection ().getResponseCode ()
并返回200(OK),不会抛出异常。即现在可以使用基本的Java方式打开HTTPS连接,因为证书现在是可信的。
但是,这对javax.ws.rs客户端没有任何明显的影响(至少在Resteasy中实现),我仍然有例外:
javax.ws.rs.ProcessingException: Unable to invoke request
at org.jboss.resteasy.client.jaxrs.engines.ApacheHttpClient4Engine.invoke(ApacheHttpClient4Engine.java:287)
at org.jboss.resteasy.client.jaxrs.internal.ClientInvocation.invoke(ClientInvocation.java:407)
at org.jboss.resteasy.client.jaxrs.internal.ClientInvocationBuilder.method(ClientInvocationBuilder.java:273)
[...]
Caused by: javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
at sun.security.ssl.Alerts.getSSLException(Alerts.java:192)
at sun.security.ssl.SSLSocketImpl.fatal(SSLSocketImpl.java:1949)
at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:302)
at sun.security.ssl.Handshaker.fatalSE(Handshaker.java:296)
at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1506)
at sun.security.ssl.ClientHandshaker.processMessage(ClientHandshaker.java:216)
at sun.security.ssl.Handshaker.processLoop(Handshaker.java:979)
at sun.security.ssl.Handshaker.process_record(Handshaker.java:914)
at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:1062)
at sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1375)
at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1403)
at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1387)
at org.apache.http.conn.ssl.SSLSocketFactory.connectSocket(SSLSocketFactory.java:535)
at org.apache.http.conn.ssl.SSLSocketFactory.connectSocket(SSLSocketFactory.java:403)
at org.apache.http.impl.conn.DefaultClientConnectionOperator.openConnection(DefaultClientConnectionOperator.java:177)
at org.apache.http.impl.conn.ManagedClientConnectionImpl.open(ManagedClientConnectionImpl.java:304)
at org.apache.http.impl.client.DefaultRequestDirector.tryConnect(DefaultRequestDirector.java:611)
at org.apache.http.impl.client.DefaultRequestDirector.execute(DefaultRequestDirector.java:446)
at org.apache.http.impl.client.AbstractHttpClient.doExecute(AbstractHttpClient.java:863)
at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:82)
at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:57)
at org.jboss.resteasy.client.jaxrs.engines.ApacheHttpClient4Engine.invoke(ApacheHttpClient4Engine.java:283)
... 90 more
Caused by: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
at sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:387)
at sun.security.validator.PKIXValidator.engineValidate(PKIXValidator.java:292)
at sun.security.validator.Validator.validate(Validator.java:260)
at sun.security.ssl.X509TrustManagerImpl.validate(X509TrustManagerImpl.java:324)
at sun.security.ssl.X509TrustManagerImpl.checkTrusted(X509TrustManagerImpl.java:229)
at sun.security.ssl.X509TrustManagerImpl.checkServerTrusted(X509TrustManagerImpl.java:124)
at sun.security.ssl.ClientHandshaker.serverCertificate(ClientHandshaker.java:1488)
... 107 more
Caused by: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
at sun.security.provider.certpath.SunCertPathBuilder.build(SunCertPathBuilder.java:146)
at sun.security.provider.certpath.SunCertPathBuilder.engineBuild(SunCertPathBuilder.java:131)
at java.security.cert.CertPathBuilder.build(CertPathBuilder.java:280)
at sun.security.validator.PKIXValidator.doBuild(PKIXValidator.java:382)
... 113 more
似乎Resteasy没有考虑“标准”密钥库。但我更愿意为其他可信密钥设置一个中央(特定于机器)的地方,而不是通过URL.openConnection
或javax.ws.rs来打扰应用程序如何使用它们。
问题
是否有可能使javax.ws.rs Client
使用与“普通”Java HTTPS连接机制相同的密钥库?
答案 0 :(得分:4)
Client
实例在ClientBuilder
API中有一个method,可让您设置SSLContext
:
public abstract ClientBuilder sslContext(SSLContext sslContext)
设置从使用此SSL上下文的客户端实例创建的Web目标创建到服务器端点的安全传输连接时将使用的SSL上下文。 SSL上下文应该初始化所有安全基础架构,包括密钥和信任管理器。
设置SSL上下文实例会重置先前指定的任何密钥库或信任库值。
<强>参数:强>
sslContext
- 安全套接字协议实现,充当安全套接字工厂或SSL引擎的工厂。不得为null
。<强>返回:强>
更新的客户端构建器实例。
<强>抛出:强>
NullPointerException
- 如果sslContext
参数为null
。
假设您已将证书添加到cacerts
信任库,则可以在创建SSLContext
实例时使用默认Client
。
Client client = ClientBuilder.newBuilder().sslContext(SSLContext.getDefault()).build();
它应该就足够了。但是,出于某种原因,上面的代码不适用于RESTEasy,但可以与Jersey一起使用。这很可能是一个RESTEasy错误。
RESTEasy documentation声明如下:
默认情况下,客户端和服务器之间的网络通信由Apache HttpComponents项目中的
HttpClient
(4.x)处理。 [...]RESTEasy和
HttpClient
做出合理的默认决策,以便可以在不引用HttpClient
的情况下使用客户端框架,但对于某些应用程序,可能需要向下钻取HttpClient
} 细节。 [...]
要自定义RESTEeasy使用的HttpClient
,请执行以下操作:
HttpClient httpClient = HttpClientBuilder.create()
.setSslcontext(SSLContext.getDefault())
.build();
ApacheHttpClient4Engine engine = new ApacheHttpClient4Engine(httpClient);
Client client = new ResteasyClientBuilder().httpEngine(engine).build();
然后你可以执行请求:
Response response = client.target("https://self-signed.badssl.com/").request().get();
System.out.println(response.getStatus());
在创建SSLContext
时,您可以加载Client
,而不是使用KeyStore
。要加载cacerts
信任库,您可以执行以下操作:
String filename = System.getProperty("java.home") +
"/lib/security/cacerts".replace('/', File.separatorChar);
FileInputStream is = new FileInputStream(filename);
KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType());
String password = "changeit";
keystore.load(is, password.toCharArray());
cacerts
'默认密码为changeit
。
然后使用以下方法之一创建Client
实例:
Client client = ClientBuilder.newBuilder().trustStore(keystore).build();
Client client = ClientBuilder.newBuilder().keyStore(keystore, password).build();
问题在于它不适用于RESTEasy,但适用于Jersey。
针对以下JAX-RS客户端API实现测试了上述解决方案:
jersey-client
(版本2.23.1)resteasy-client
(版本3.0.18.Final)