喷雾客户端 sendReceive 抛出 SSLHandshakeException
Spray-client sendReceive throws SSLHandshakeException
我正在尝试使用 https 连接 spray-client 以限制休息 api。问题是远程服务器的证书未注册为受信任的,简单的 Get() 连接随后被 SSLHandshakeException 拒绝,我很难找到有关如何使其工作的任何信息。这在某种程度上确实可以在我的本地计算机上运行,而无需进行任何更改。
我找到了有关如何将证书放入 jvm 信任库的教程,但是由于我使用的是 dokku/docker,据我所知,jvm 实例是特定于容器的(或?)。即使将来我可能会在不同的机器上重新部署应用程序,我还是希望在应用程序中定义它而不是每次都设置 jvm。
这是我第一次以编程方式面对 SSL,因此我可能会对它的工作原理做出错误的假设。你能帮忙吗?
我不是 scala 的专家,也从未使用过 spray-client,但我会根据我的 Java 经验尽力帮助您。
您有两个选择,从具有服务器证书 (SECURE) 的密钥库中使用 TrustManagerFactory 初始化 SSLContext
File keyStoreFile = new File("./myKeyStore");
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
ks.load(new FileInputStream(keyStoreFile), "keyStorePassword".toCharArray());
TrustManagerFactory tmf = TrustManagerFactory.getInstance("X509");
tmf.init(ks);
SSLContext sc = SSLContext.getInstance("TLS");
sc.init(null, tmf.getTrustManagers(), new java.security.SecureRandom());
或创建一个接受任何证书(不安全)的虚拟 TrustManagerFactory
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import javax.net.ssl.X509TrustManager;
public class DummyTrustManager implements X509TrustManager {
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
/* (non-Javadoc)
* @see javax.net.ssl.X509TrustManager#checkClientTrusted(X509Certificate[], java.lang.String)
*/
public void checkClientTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
}
/* (non-Javadoc)
* @see javax.net.ssl.X509TrustManager#checkServerTrusted(X509Certificate[], java.lang.String)
*/
public void checkServerTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
}
}
通过这种方式初始化 SSLContext (it is very similar in spray-client)
SSLContext sc = SSLContext.getInstance("TLS");
sc.init(null, new TrustManager[] { new DummyTrustManager() }, new java.security.SecureRandom());
我不懂 Scala 语法,但翻译给你应该不难。
希望这对您有所帮助。
编辑(由 Matej Briškár 建议):以上是正确的方法,但是对于 spray-client 来说并不是那么容易。要使 sendReceive
使用 SSL,您需要先建立连接,然后将此连接传递给 sendReceive
。
首先如上所述创建隐式信任管理器。例如:
implicit def sslContext: SSLContext = {
val context = SSLContext.getInstance("TLS")
context.init(null, Array[TrustManager](new DummyTrustManager), new SecureRandom())
context
}
请注意,此连接会在一段时间后超时,因此您可能需要更改此默认行为。
然后您需要建立将使用此隐式的连接,例如:
val connection = {
Await.result((IO(Http) ? HostConnectorSetup(host, port = 443, sslEncryption = true)).map { case HostConnectorInfo(hostConnector, _) => hostConnector }, timeout.duration)
}
注意:host
表示您要达到的 URL。另外 timeout
来自此代码段之外。
终于可以使用sendReceive(connection)
访问SSL加密主机了。
注:原编辑有参考:
According to discussion online the issue is going to be fixed though.
但是,讨论是从 2013 年开始的,现在是 2016 年了。需要建立连接才能使 SSL 正常工作的问题似乎仍然存在。不确定讨论是否相关。
这是我的 2 美分,如果你只是想以不安全的方式来做,我只是创建我的发送接收方法来发送(Http Request,HostConnectorSetup)而不是 HttpRequest
import java.security.SecureRandom
import java.security.cert.X509Certificate
import javax.net.ssl.{SSLContext, TrustManager, X509TrustManager}
import akka.actor.ActorRefFactory
import akka.io.IO
import akka.pattern.ask
import akka.util.Timeout
import spray.can.Http
import spray.can.Http.HostConnectorSetup
import spray.client.pipelining._
import spray.http.{HttpResponse, HttpResponsePart}
import spray.io.ClientSSLEngineProvider
import spray.util._
import scala.concurrent.ExecutionContext
import scala.concurrent.duration._
object Test {
// prepare your sslContext and engine Provider
implicit lazy val engineProvider = ClientSSLEngineProvider(engine => engine)
implicit lazy val sslContext: SSLContext = {
val context = SSLContext.getInstance("TLS")
context.init(null, Array[TrustManager](new DummyTrustManager), new SecureRandom)
context
}
private class DummyTrustManager extends X509TrustManager {
def isClientTrusted(cert: Array[X509Certificate]): Boolean = true
def isServerTrusted(cert: Array[X509Certificate]): Boolean = true
override def getAcceptedIssuers: Array[X509Certificate] = Array.empty
override def checkClientTrusted(x509Certificates: Array[X509Certificate], s: String): Unit = {}
override def checkServerTrusted(x509Certificates: Array[X509Certificate], s: String): Unit = {}
}
// rewrite sendReceiveMethod fron spray.client.pipelining
def mySendReceive(implicit refFactory: ActorRefFactory, executionContext: ExecutionContext,
futureTimeout: Timeout = 60.seconds): SendReceive = {
val transport = IO(Http)(actorSystem)
// HttpManager actually also accepts Msg (HttpRequest, HostConnectorSetup)
request =>
val uri = request.uri
val setup = HostConnectorSetup(uri.authority.host.toString, uri.effectivePort, uri.scheme == "https")
transport ? (request, setup) map {
case x: HttpResponse => x
case x: HttpResponsePart => sys.error("sendReceive doesn't support chunked responses, try sendTo instead")
case x: Http.ConnectionClosed => sys.error("Connection closed before reception of response: " + x)
case x => sys.error("Unexpected response from HTTP transport: " + x)
}
}
// use mySendReceive instead spray.client.pipelining.sendReceive
}
我正在尝试使用 https 连接 spray-client 以限制休息 api。问题是远程服务器的证书未注册为受信任的,简单的 Get() 连接随后被 SSLHandshakeException 拒绝,我很难找到有关如何使其工作的任何信息。这在某种程度上确实可以在我的本地计算机上运行,而无需进行任何更改。
我找到了有关如何将证书放入 jvm 信任库的教程,但是由于我使用的是 dokku/docker,据我所知,jvm 实例是特定于容器的(或?)。即使将来我可能会在不同的机器上重新部署应用程序,我还是希望在应用程序中定义它而不是每次都设置 jvm。
这是我第一次以编程方式面对 SSL,因此我可能会对它的工作原理做出错误的假设。你能帮忙吗?
我不是 scala 的专家,也从未使用过 spray-client,但我会根据我的 Java 经验尽力帮助您。
您有两个选择,从具有服务器证书 (SECURE) 的密钥库中使用 TrustManagerFactory 初始化 SSLContext
File keyStoreFile = new File("./myKeyStore");
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
ks.load(new FileInputStream(keyStoreFile), "keyStorePassword".toCharArray());
TrustManagerFactory tmf = TrustManagerFactory.getInstance("X509");
tmf.init(ks);
SSLContext sc = SSLContext.getInstance("TLS");
sc.init(null, tmf.getTrustManagers(), new java.security.SecureRandom());
或创建一个接受任何证书(不安全)的虚拟 TrustManagerFactory
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import javax.net.ssl.X509TrustManager;
public class DummyTrustManager implements X509TrustManager {
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
/* (non-Javadoc)
* @see javax.net.ssl.X509TrustManager#checkClientTrusted(X509Certificate[], java.lang.String)
*/
public void checkClientTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
}
/* (non-Javadoc)
* @see javax.net.ssl.X509TrustManager#checkServerTrusted(X509Certificate[], java.lang.String)
*/
public void checkServerTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
}
}
通过这种方式初始化 SSLContext (it is very similar in spray-client)
SSLContext sc = SSLContext.getInstance("TLS");
sc.init(null, new TrustManager[] { new DummyTrustManager() }, new java.security.SecureRandom());
我不懂 Scala 语法,但翻译给你应该不难。
希望这对您有所帮助。
编辑(由 Matej Briškár 建议):以上是正确的方法,但是对于 spray-client 来说并不是那么容易。要使 sendReceive
使用 SSL,您需要先建立连接,然后将此连接传递给 sendReceive
。
首先如上所述创建隐式信任管理器。例如:
implicit def sslContext: SSLContext = {
val context = SSLContext.getInstance("TLS")
context.init(null, Array[TrustManager](new DummyTrustManager), new SecureRandom())
context
}
请注意,此连接会在一段时间后超时,因此您可能需要更改此默认行为。
然后您需要建立将使用此隐式的连接,例如:
val connection = {
Await.result((IO(Http) ? HostConnectorSetup(host, port = 443, sslEncryption = true)).map { case HostConnectorInfo(hostConnector, _) => hostConnector }, timeout.duration)
}
注意:host
表示您要达到的 URL。另外 timeout
来自此代码段之外。
终于可以使用sendReceive(connection)
访问SSL加密主机了。
注:原编辑有参考:
According to discussion online the issue is going to be fixed though.
但是,讨论是从 2013 年开始的,现在是 2016 年了。需要建立连接才能使 SSL 正常工作的问题似乎仍然存在。不确定讨论是否相关。
这是我的 2 美分,如果你只是想以不安全的方式来做,我只是创建我的发送接收方法来发送(Http Request,HostConnectorSetup)而不是 HttpRequest
import java.security.SecureRandom
import java.security.cert.X509Certificate
import javax.net.ssl.{SSLContext, TrustManager, X509TrustManager}
import akka.actor.ActorRefFactory
import akka.io.IO
import akka.pattern.ask
import akka.util.Timeout
import spray.can.Http
import spray.can.Http.HostConnectorSetup
import spray.client.pipelining._
import spray.http.{HttpResponse, HttpResponsePart}
import spray.io.ClientSSLEngineProvider
import spray.util._
import scala.concurrent.ExecutionContext
import scala.concurrent.duration._
object Test {
// prepare your sslContext and engine Provider
implicit lazy val engineProvider = ClientSSLEngineProvider(engine => engine)
implicit lazy val sslContext: SSLContext = {
val context = SSLContext.getInstance("TLS")
context.init(null, Array[TrustManager](new DummyTrustManager), new SecureRandom)
context
}
private class DummyTrustManager extends X509TrustManager {
def isClientTrusted(cert: Array[X509Certificate]): Boolean = true
def isServerTrusted(cert: Array[X509Certificate]): Boolean = true
override def getAcceptedIssuers: Array[X509Certificate] = Array.empty
override def checkClientTrusted(x509Certificates: Array[X509Certificate], s: String): Unit = {}
override def checkServerTrusted(x509Certificates: Array[X509Certificate], s: String): Unit = {}
}
// rewrite sendReceiveMethod fron spray.client.pipelining
def mySendReceive(implicit refFactory: ActorRefFactory, executionContext: ExecutionContext,
futureTimeout: Timeout = 60.seconds): SendReceive = {
val transport = IO(Http)(actorSystem)
// HttpManager actually also accepts Msg (HttpRequest, HostConnectorSetup)
request =>
val uri = request.uri
val setup = HostConnectorSetup(uri.authority.host.toString, uri.effectivePort, uri.scheme == "https")
transport ? (request, setup) map {
case x: HttpResponse => x
case x: HttpResponsePart => sys.error("sendReceive doesn't support chunked responses, try sendTo instead")
case x: Http.ConnectionClosed => sys.error("Connection closed before reception of response: " + x)
case x => sys.error("Unexpected response from HTTP transport: " + x)
}
}
// use mySendReceive instead spray.client.pipelining.sendReceive
}