创建示例HTTPS服务器以获取乐趣和收益

通常,在开发人员或/和针对实际场景进行测试期间,我们(开发人员)面临着运行成熟的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 ,我们可以用来配置KeyManagerTrustManagerSSLContext

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"

此外,我们仅配置TLSTLS v1.0TLS v1.1TLS 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.CloseAll
    system.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 HttpsServer
  server.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:10999

HTTP/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: 4

OK!

或者,可以将证书与curl命令一起传递,以便进行完整的SSL证书验证,例如:

$  curl -i --cacert src/main/resources/certificate.crt  https://localhost:10999

HTTP/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: 4

OK!

一切看起来不错,但是我们可以使用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);
 
    @Test
    public 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());
 
    @Test
    public 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是可行的选择。

翻译自: https://www.javacodegeeks.com/2015/10/creating-sample-https-server-for-fun-and-profit.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值