1

我正在尝试使用 https 使喷雾客户端连接到受限的 rest api。问题是远程服务器的证书未注册为受信任,然后简单的 Get() 连接被 SSLHandshakeException 拒绝,我很难找到有关如何使其工作的任何信息。这可以在我的本地机器上以某种方式工作,而无需更改某些内容。

我找到了有关如何将证书放入 jvm 信任库的教程,但是由于我使用的是 dokku/docker,AFAIK jvm 实例是特定于容器的(或者?)。即使将来我可能会在不同的机器上重新部署应用程序,我希望在应用程序中定义它,而不是每次都设置 jvm。

这是我第一次以编程方式面对 SSL,所以我可能会对它的工作方式做出错误的假设。你能帮我吗?

4

2 回答 2

1

我不是 scala 方面的专家,也从未使用过 spray-client,但我会根据我的 Java 经验尝试为您提供帮助。

您有两个选择,使用服务器证书(安全)的密钥库中的 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(在 spray-client 中非常相似

SSLContext sc = SSLContext.getInstance("TLS");
sc.init(null, new TrustManager[] { new DummyTrustManager() }, new java.security.SecureRandom());

我不知道 Scala 语法,但应该不难翻译给你。

希望这可以帮助。


编辑(由 Matej Briškár 建议):以上是正确的方法,但是对于喷雾客户端来说并不容易。要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 加密主机访问。


注意:原始编辑有一个参考:

根据网上的讨论,这个问题将得到解决。

然而,讨论是从 2013 年开始,现在是 2016 年。需要建立连接才能使 SSL 工作的问题似乎仍然存在。不知道讨论是否相关,还有。

于 2015-03-11T20:04:54.963 回答
1

如果您只想以不安全的方式执行此操作,这是我的 2 美分,我只是创建我的 sendReceive 方法来发送 (HttpRequest, 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
}
于 2016-07-10T13:06:36.817 回答