【问题标题】:How to define SSLContext with Spray https client?如何使用 Spray https 客户端定义 SSLContext?
【发布时间】:2015-02-08 16:10:04
【问题描述】:

我想将 http 请求发布到具有给定 ca 证书的安全服务器。

我使用的是 Spray 1.3.1,代码如下所示:

val is = this.getClass().getResourceAsStream("/cacert.crt")

val cf: CertificateFactory = CertificateFactory.getInstance("X.509")

val caCert: X509Certificate = cf.generateCertificate(is).asInstanceOf[X509Certificate];

val tmf: TrustManagerFactory  = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
val ks: KeyStore = KeyStore.getInstance(KeyStore.getDefaultType());
ks.load(null); 
ks.setCertificateEntry("caCert", caCert);

tmf.init(ks);

implicit val sslContext: SSLContext = SSLContext.getInstance("TLS");
sslContext.init(null, tmf.getTrustManagers(), null);

implicit val timeout: Timeout = Timeout(15.seconds)
import spray.httpx.RequestBuilding._

val respFuture = (IO(Http) ? Post( uri=Uri(url), content="my content")).mapTo[HttpResponse]

问题是定义的隐式 SSLContext 没有被采用,我在运行时得到:“无法找到请求目标的有效证书路径”。

我如何定义一个 SSLContext 以与喷雾客户端一起使用?

【问题讨论】:

    标签: ssl https spray spray-client


    【解决方案1】:

    我使用以下内容在 spray 中定义 SSLContext。就我而言,我使用了一个非常宽松的上下文,它不验证远程服务器的证书。基于this post 中的第一个解决方案 - 对我有用。

    import java.security.SecureRandom
    import java.security.cert.X509Certificate
    import javax.net.ssl.{SSLContext, X509TrustManager, TrustManager}
    
    import akka.actor.ActorRef
    import akka.io.IO
    import akka.util.Timeout
    import spray.can.Http
    
    import scala.concurrent.Future
    
    trait HttpClient {
      /** For the HostConnectorSetup ask operation. */
      implicit val ImplicitPoolSetupTimeout: Timeout = 30 seconds
    
      val hostName: String
      val hostPort: Int
    
      implicit val sslContext = {
        /** Create a trust manager that does not validate certificate chains. */
        val permissiveTrustManager: TrustManager = new X509TrustManager() {
          override def checkClientTrusted(chain: Array[X509Certificate], authType: String): Unit = {
          }
          override def checkServerTrusted(chain: Array[X509Certificate], authType: String): Unit = {
          }
          override def getAcceptedIssuers(): Array[X509Certificate] = {
            null
          }
        }
    
        val initTrustManagers = Array(permissiveTrustManager)
        val ctx = SSLContext.getInstance("TLS")
        ctx.init(null, initTrustManagers, new SecureRandom())
        ctx
      }
    
      def initClientPool(): Future[ActorRef] = {
        val hostPoolFuture = for {
          Http.HostConnectorInfo(connector, _) <- IO(Http) ? Http.HostConnectorSetup(hostName, port = hostPort,
            sslEncryption = true)
        } yield connector
      }
    }
    

    【讨论】:

      【解决方案2】:

      我想出了这个sendReceive 的替代品,它允许传递自定义的SSLContext(作为implicit

      def mySendReceive( request: HttpRequest )( implicit uri: spray.http.Uri, ec: ExecutionContext, futureTimeout: Timeout = 60.seconds, sslContext: SSLContext = SSLContext.getDefault): Future[ HttpResponse ] = {
      
          implicit val clientSSLEngineProvider = ClientSSLEngineProvider { _ =>
              val engine = sslContext.createSSLEngine( )
              engine.setUseClientMode( true )
              engine
          }
      
          for {
              Http.HostConnectorInfo( connector, _ ) <- IO( Http ) ? Http.HostConnectorSetup( uri.authority.host.address, port = uri.authority.port, sslEncryption = true )
              response <- connector ? request
          } yield response match {
              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 )
          }
      }
      

      然后像“平常”一样使用它(几乎见下文):

      val pipeline: HttpRequest => Future[ HttpResponse ] = mySendReceive
      pipeline( Get( uri ) ) map processResponse
      

      有几件事我真的不喜欢:

      • 这是一个黑客。我希望spray-client 允许本地支持自定义SSLContext。这些在开发和测试期间非常有用,通常强制自定义TrustManagers

      • 有一个implicit uri: spray.http.Uri 参数可以避免对连接器上的主机和端口进行硬编码。所以uri 必须声明为implicit

      对此代码的任何改进,甚至更好,spray-client 的补丁,都是非常受欢迎的(SSLEngine 创建的外部化是显而易见的)

      【讨论】:

        【解决方案3】:

        我工作的最短时间是这样的:

        IO(Http) ! HostConnectorSetup(host = Conf.base.getHost, port = 443, sslEncryption = true)
        

        即@reed-sandberg 的答案中有什么,但似乎不需要询问模式。我没有将连接参数传递给sendReceive,而是:

        // `host` is the host part of the service
        //
        def addHost = { req: HttpRequest => req.withEffectiveUri(true, Host(host, 443)) }
        
        val pipeline: HttpRequest => Future[Seq[PartitionInfo]] = (
            addHost
            ~> sendReceive
            ~> unmarshal[...]
        )
        

        这似乎可行,但我很想知道这种方法是否有缺点。

        我同意所有spray-client SSL 支持批评。像这样的事情如此困难,这很尴尬。我可能花了 2 天时间,合并来自各种来源(SO、spray 文档、邮件列表)的数据。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2015-11-21
          • 1970-01-01
          • 2017-07-28
          • 1970-01-01
          • 2011-01-09
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多