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

dou*_*lep 8 jax-rs resteasy

假设我们需要信任自签名的SSL证书.例如,让我们使用https://self-signed.badssl.com/.

由于签名者不是"适当的"权限,Java不信任它并拒绝连接到该服务器.然而,之后

$ cd $JAVA_HOME/jre/lib/security
$ keytool -import -trustcacerts -alias ... -file ... -keystore cacerts
Run Code Online (Sandbox Code Playgroud)

并重新启动应用程序,以下代码有效:

new URL ("https://self-signed.badssl.com/").openConnection ().getResponseCode ()
Run Code Online (Sandbox Code Playgroud)

并返回200(OK),不抛出异常.即打开HTTPS连接的基本Java方法现在可以使用,因为证书现在是可信的.

但是,这对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
Run Code Online (Sandbox Code Playgroud)

似乎Resteasy没有考虑"标准"密钥库.但我更愿意为其他可信密钥设置一个中央(特定于机器)的位置,而不是打扰应用程序如何使用它们,URL.openConnection或使用javax.ws.rs.

问题 是否可以使javax.ws.rs Client使用 "普通"Java HTTPS连接机制相同的密钥库?

cas*_*lin 6

在创建Client实例时设置SSL上下文

ClientBuilderAPI中,有一种方法可让您设置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();
Run Code Online (Sandbox Code Playgroud)

应该是足够的。但是,由于某种原因,以上代码不适用于RESTEasy,但适用于Jersey。这很可能是RESTEasy错误。

标准解决方案不适用于RESTEasy。我该怎么办?

RESTEasy 文档指出以下内容:

客户端和服务器之间的网络通信默认情况下由HttpClientApache HttpComponents项目中的(4.x)在RESTEasy中进行处理。[...]

RESTEasy并HttpClient做出合理的默认决定,以便可以使用客户端框架而无需进行引用HttpClient,但是对于某些应用程序,可能有必要深入研究HttpClient细节。[...]

要自定义HttpClientRESTEeasy 所使用的,请执行以下操作:

HttpClient httpClient = HttpClientBuilder.create()
                                         .setSslcontext(SSLContext.getDefault())
                                         .build();

ApacheHttpClient4Engine engine = new ApacheHttpClient4Engine(httpClient);
Client client = new ResteasyClientBuilder().httpEngine(engine).build();
Run Code Online (Sandbox Code Playgroud)

然后,您可以执行请求:

Response response = client.target("https://self-signed.badssl.com/").request().get();
System.out.println(response.getStatus());
Run Code Online (Sandbox Code Playgroud)

SSL上下文是否有替代方案?

您可以加载一个,而不是SSLContext在创建时使用。要加载信任库,您可以执行以下操作:ClientKeyStorecacerts

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());
Run Code Online (Sandbox Code Playgroud)

cacerts“默认密码为changeit

然后Client使用以下方法之一创建实例:

Client client = ClientBuilder.newBuilder().trustStore(keystore).build();
Run Code Online (Sandbox Code Playgroud)
Client client = ClientBuilder.newBuilder().keyStore(keystore, password).build();
Run Code Online (Sandbox Code Playgroud)

问题在于它不适用于RESTEasy,但适用于Jersey。


上面提到的解决方案已针对以下JAX-RS客户端API实现进行了测试: