是否可以为javax.ws.rs重用Java可信证书(Resteasy实现)?

时间:2016-07-21 13:14:16

标签: jax-rs resteasy

假设我们需要信任自签名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连接机制相同的密钥库?

1 个答案:

答案 0 :(得分:4)

在创建Client实例

时设置SSL上下文

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。我该怎么办?

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());

是否有SSL上下文的替代方法?

在创建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实现测试了上述解决方案: