在Kotlin和Ktor中使用SSL

595 阅读3分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 18 天,点击查看活动详情

在Kotlin和Ktor中使用SSL

当你决定看这篇文章的时候, 你已经用Kotlin和Ktor开发了一些服务.

通常情况下, 你的Ktor服务将安全地坐在一个反向代理后面, 它将为你处理所有基本的安全问题, 包括SSL.

而在你的测试环境中, 你将在完全没有SSL的情况下运行, 简单地绕过所有的安全. 通常都是这样的情况.

但在我的案例中, 我有一个特别的HTTP客户端需要测试, 它只支持HTTPS.

不幸的是, Ktor的官方文档对自签名证书非常混乱. 首先, 它只涵盖了你使用applcation.conf来设置服务器的情况.

因此, 我决定在这里记录我对如何用Ktor建立你自己的HTTPS测试服务器的发现.

首先, 你需要额外的依赖性来支持SSL.

在你的build.gradle.kts文件中添加下面的依赖:

val ktorVersion = "1.4.0"
dependencies {
    ...
    testImplementation("io.ktor:ktor-network-tls-certificates:$ktorVersion")
}

请注意, 我在这里正使用 testImplementation , 特别是, 所以你不会在你的生产代码中使用这个解决方案.

现在我们可以开始真正的工作了.

因此, 我们要在HTTPS模式下启动我们的Ktor服务器. 为此, 我们需要定义一个密钥库. 你可以从命令行中做到这一点, 但我希望能够通过编程来做到这一点.

首先, 我们需要两个必须对齐的重要变量: 一个是我们的证书的alias, 一个是钥匙库的password:

val alias = "certificateAlias"
val pass = "bcd"

接下来, 我们将定义我们的钥匙库. 密钥库可以包含多个使用不同算法的证书, 但在这里我将只使用一个证书:

val keystore = buildKeyStore {
 certificate(alias) {
 hash = HashAlgorithm.SHA256
        sign = SignatureAlgorithm.ECDSA
        keySizeInBits = 256
        password = pass
     }      // More certificates come here } 

接下来是我们服务器的定义. 这就是事情开始变得有点复杂的地方:

val server = embeddedServer(Netty, applicationEngineEnvironment {
    ...
}) 

注意, 这与我们通常定义Ktor服务器的方式不同:

val server = embeddedServer(Netty, 8080) {
    ...
}

这两段代码可能看起来很相似, 但上面的块代表ApplicationEngineEnvironment, 而下面的块代表 Module. 这些是截然不同的. 为了设置SSL, 我们需要使用ApplicationEngineEnvironment.

有了ApplicationEngineEnvironment之后, 我们现在开始定义sslConnector:

applicationEngineEnvironment {
 sslConnector(keystore,
            alias,
            { "".toCharArray() } ,
            { pass.toCharArray() } ) {
            ...
    }
} 

我们在用之前定义的aliaspasswordkeystore.

现在, 由于我们没有机会定义我们的服务器在哪个端口启动, 我们现在不妨这样做:

sslConnector(...) {
 port = 8443
    keyStorePath = keyStore.asFile.absoluteFile
    ...
}

而且, 正如你所看到的, 我们也必须在这里定义密钥存储路径.

asFile是为了简单而定义的扩展属性:

private val KeyStore.asFile: File
    get() {
        val keyStoreFile = File("build/temp.jks")
        this.saveToFile(keyStoreFile, pass)
        return keyStoreFile
    }

现在, 如果你触发了server.start()的话, 你应该能够在面板中看到:

Responding at https://0.0.0.0:8443

但是, 等等, 我们是不是忘记了什么? 拥有一个没有定义任何路由的服务器是很没有意义的.

你可能很想照常做:

applicationEngineEnvironment {
 sslConnector(...) {
        ...
    }
    routing {
        ...
    }
} 

但这是行不通的. 记住, 通常我们是在模块的上下文中操作, 但在这里我们是在ApplicationEngineEnvironment的上下文中.

所以, 我们需要先声明一个模块:

applicationEngineEnvironment {
 sslConnector(...) {
        ...
    }
    module {
        ...
    }
} 

现在我们可以像往常一样声明我们的路由:

applicationEngineEnvironment {
 sslConnector(...) {
        ...
    }
    module {
        routes { 
            ...
        }
    }
} 

而这就是了. 你可以在这里看到完整的例子.

请记住, 你应该只为测试目的而使用这个, 而不是在生产中使用.

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 18 天,点击查看活动详情