Как определить SSLContext с клиентом Spray https?
Я хочу отправлять http-запросы на защищенный сервер с заданным сертификатом.
Я использую 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 для использования с клиентом спрея?
3 ответа
Я использую следующее, чтобы определить SSLContext в спрей. В моем случае я использую очень разрешающий контекст, который не проверяет сертификат удаленного сервера. Исходя из первого решения в этом посте - у меня работает.
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
}
}
Я придумал эту замену для 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)
Самое короткое, что я получил на работу, это:
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[...]
)
Кажется, это работает, но мне, естественно, было бы интересно узнать, есть ли недостатки этого подхода.
Я согласен со всей критикой поддержки SSL спрей-клиентов. Неловко, что что-то подобное так сложно. Я, вероятно, потратил на это 2 дня, объединяя данные из разных источников (SO, документация по спрею, список рассылки)