通常,在开发人员或/和针对实际场景进行测试期间,我们(开发人员)面临着运行成熟的HTTPS服务器的需求,可能同时进行一些模拟。 在JVM平台上,除非您知道适合此工作的正确工具,否则它过去并不是一件容易的事。 在这篇文章中,我们将使用出色的Spray框架和Scala语言创建一个完全可操作的HTTPS服务器的框架。
首先,我们需要分别生成x509证书和私钥。 幸运的是,使用openssl命令行工具非常容易。
openssl req -x509 -sha256 -newkey rsa:2048 -keyout certificate.key -out certificate.crt -days 1024 -nodes
在JVM平台上,我们的基本目标是拥有Java密钥库 ( JKS ),这是安全证书的存储库。 但是,要将我们新生成的证书导入JKS ,我们必须以PKCS#12格式导出它,然后从中创建密钥库。 同样,在resque上使用openssl 。
openssl pkcs12 -export -in certificate.crt -inkey certificate.key -out server.p12 -name sample-https-server -password pass:change-me-please
请注意,存档服务器.p12受密码保护。 现在,最后一步涉及JDK发行版中的命令行工具keytool 。
keytool -importkeystore -srcstorepass change-me-please -destkeystore sample-https-server.jks -deststorepass change-me-please -srckeystore server.p12 -srcstoretype PKCS12 -alias sample-https-server
结果是受密码保护的sample-https-server.jks密钥库,我们可以在HTTPS服务器应用程序中使用它来配置SSL上下文。 Spray有非常好的文档和大量示例,其中一个示例SslConfiguration ,我们可以用来配置KeyManager , TrustManager和SSLContext 。
trait SslConfiguration {// If there is no SSLContext in scope implicitly, the default SSLContext is // going to be used. But we want non-default settings so we are making // custom SSLContext available here.implicit def sslContext: SSLContext = {val keyStoreResource = "/sample-https-server.jks"val password = "change-me-please"val keyStore = KeyStore.getInstance("jks")keyStore.load(getClass.getResourceAsStream(keyStoreResource), password.toCharArray)val keyManagerFactory = KeyManagerFactory.getInstance("SunX509")keyManagerFactory.init(keyStore, password.toCharArray)val trustManagerFactory = TrustManagerFactory.getInstance("SunX509")trustManagerFactory.init(keyStore)val context = SSLContext.getInstance("TLS")context.init(keyManagerFactory.getKeyManagers, trustManagerFactory.getTrustManagers, new SecureRandom)context}// If there is no ServerSSLEngineProvider in scope implicitly, // the default one is going to be used. But we would like to configure// cipher suites and protocols so we are making a custom ServerSSLEngineProvider// available here.implicit def sslEngineProvider: ServerSSLEngineProvider = {ServerSSLEngineProvider { engine =>engine.setEnabledCipherSuites(Array("TLS_RSA_WITH_AES_128_CBC_SHA"))engine.setEnabledProtocols(Array( "TLSv1", "TLSv1.1", "TLSv1.2" ))engine}}
}
这里有几点要强调。 首先,使用先前创建的我们自己的密钥库(为方便起见,我们将其存储为类路径资源):
val keyStoreResource = "/sample-https-server.jks"
val password = "change-me-please"
此外,我们仅配置TLS ( TLS v1.0 , TLS v1.1和TLS v1.2 ),不支持SSLv3 。 除此之外,我们仅启用一种密码: TLS_RSA_WITH_AES_128_CBC_SHA 。 这样做主要是为了说明,因为在大多数情况下都可以启用所有受支持的密码。
engine.setEnabledCipherSuites(Array("TLS_RSA_WITH_AES_128_CBC_SHA"))
engine.setEnabledProtocols(Array( "TLSv1", "TLSv1.1", "TLSv1.2" ))
这样,我们就可以创建一个真正的HTTPS服务器了,这要归功于Spray框架只有几行:
class HttpsServer(val route: Route = RestService.defaultRoute) extends SslConfiguration {implicit val system = ActorSystem()implicit val timeout: Timeout = 3 seconds val settings = ServerSettings(system).copy(sslEncryption = true)val handler = system.actorOf(Props(new RestService(route)), name = "handler")def start(port: Int) = Await.ready(IO(Http) ? Http.Bind(handler, interface = "localhost", port = port, settings = Some(settings)), timeout.duration)def stop() = {IO(Http) ? Http.CloseAllsystem.stop(handler)}
}
任何根本不执行任何操作的HTTPS服务器不是很有用。 这就是路由属性发挥作用的地方:使用Spray路由扩展,我们将映射(或路由)传递给HTTP服务参与者 ( RestService )直接处理请求。
class RestService(val route: Route) extends HttpServiceActor with ActorLogging {def receive = runRoute {route}
}
使用默认路由就是:
object RestService {val defaultRoute = path("") {get {complete {"OK!\n"}}}
}
基本上,这就是我们所需要的,我们的HTTPS服务器已准备好进行测试! 运行它的最简单方法是使用Scala应用程序。
object HttpsServer extends App {val server = new HttpsServerserver.start(10999)
}
尽管是用Scala编写的,但我们可以轻松地将其嵌入到任何Java应用程序中(对于Java开发人员来说,使用一些非标准的命名约定),例如:
public class HttpsServerRunner {public static void main(String[] args) {final HttpsServer server = new HttpsServer(RestService$.MODULE$.defaultRoute());server.start(10999);}
}
一旦启动并运行(最简单的方法是sbt run ),就可以从浏览器或使用curl命令行客户端访问我们简单的HTTPS服务器的公开默认路由( -k命令行参数关闭SSL证书验证) :
$ curl -ki https://localhost:10999HTTP/1.1 200 OK
Server: spray-can/1.3.3
Date: Sun, 04 Oct 2015 01:25:47 GMT
Content-Type: text/plain; charset=UTF-8
Content-Length: 4OK!
或者,可以将证书与curl命令一起传递,以便进行完整的SSL证书验证,例如:
$ curl -i --cacert src/main/resources/certificate.crt https://localhost:10999HTTP/1.1 200 OK
Server: spray-can/1.3.3
Date: Sun, 04 Oct 2015 01:28:05 GMT
Content-Type: text/plain; charset=UTF-8
Content-Length: 4OK!
一切看起来不错,但是我们可以使用HTTPS服务器作为集成测试套件的一部分来验证/存根/模拟,例如,与第三方服务的交互吗? 答案是肯定的,这要归功于JUnit规则。 让我们看一下HttpsServerRule的最简单实现:
class HttpsServerRule(@BeanProperty val port: Int, val route: Route) extends ExternalResource {val server = new HttpsServer(route)override def before() = server.start(port)override def after() = server.stop()
}object HttpsServerRule {def apply(port: Int) = new HttpsServerRule(port, RestService.defaultRoute);def apply(port: Int, route: Route) = new HttpsServerRule(port, route);
}
我们默认实现的JUnit测试用例使用了出色的RestAssured库,该库提供了Java DSL,可轻松测试REST服务。
public class DefaultRestServiceTest {@Rule public HttpsServerRule server = HttpsServerRule$.MODULE$.apply(65200);@Testpublic void testServerIsUpAndRunning() {given().baseUri("https://localhost:" + server.getPort()).auth().certificate("/sample-https-server.jks", "change-me-please").when().get("/").then().body(containsString("OK!"));}
}
可以肯定的是,您对默认实现无法做很多事情,因此提供自定义选项是必不可少的选择。 幸运的是,我们很早就通过接受路线来解决了这一问题。
object CustomRestService {val route = path("api" / "user" / IntNumber) { id =>get {complete {"a@b.com"}}}
}
这是一个测试用例:
public class CustomRestServiceTest {@Rule public HttpsServerRule server = HttpsServerRule$.MODULE$.apply(65201, CustomRestService$.MODULE$.route());@Testpublic void testServerIsUpAndRunning() {given().baseUri("https://localhost:" + server.getPort()).auth().certificate("/sample-https-server.jks", "change-me-please").when().get("/api/user/1").then().body(containsString("a@b.com"));}
}
事实证明,创建完善的HTTPS服务器一点也不难,而且一旦您知道执行此操作的正确工具,它可能真的很有趣。 Spray框架是那些魔术工具之一。 你们中许多人都知道, Spray将被Akka HTTP取代,后者最近已经发布了1.0版本,但目前缺少许多功能(包括HTTPS支持),因此Spray是可行的选择。
- 完整的项目可以在Github上找到 。
翻译自: https://www.javacodegeeks.com/2015/10/creating-sample-https-server-for-fun-and-profit.html