如何在KMM KTOR中信任自我签名的证书?

发布于 2025-02-04 04:15:28 字数 4446 浏览 4 评论 0原文

我有一台具有自签名证书的服务器,没有域名(仅静态IP地址),该服务器仅为Android和iOS客户端提供API,因此IP和自签名证书足以满足我的目的。

移动客户端是在KMM上写的,它使用KTOR客户端执行请求:

apigateway.kt:

class ApiGateway(
    private val rootUrl: String,
    private val clientId: String,
    private val secretKey: String,
    private val accountManager: IAccountManagerGateway,
    private val getDeviceName: GetDeviceNameUseCase,
) : IApiGateway {
    private val client = HttpClient {
        installContentNegotiation()
        installLogging()
        configureRequest()
        configureResponseValidator()
        installAuth()
    }
    private var account: Account? = this.accountManager.getAccount()

    private fun HttpClientConfig<*>.configureRequest() {
        defaultRequest {
            url([email protected])
            accept(ContentType.Application.Json)
            contentType(ContentType.Application.Json)
        }
    }

    private fun HttpClientConfig<*>.configureResponseValidator() {
        HttpResponseValidator {
            validateResponse { // Stub
                when (it.status.value) {
                    in 300..399 -> throw ClientException(it.bodyAsText())
                    in 400..499 -> throw ClientException(it.bodyAsText())
                    in 500..599 -> throw ClientException(it.bodyAsText())
                }
            }
        }
    }

    private fun HttpClientConfig<*>.installAuth() {
        install(Auth) {
            bearer {
                sendWithoutRequest { request ->
                    val url = request.url.build().toString()
                    !(
                            url.contains("/api/oauth2/token")
                                    || (url.contains("/api/users") &&
                                            request.method == HttpMethod.Post
                                    )
                    )
                }
                loadTokens {
                    [email protected]?.let {
                        BearerTokens(accessToken = it.accessToken, refreshToken = it.refreshToken)
                    }
                }
                refreshTokens {
                    [email protected]?.let {
                        [email protected]()
                        BearerTokens(accessToken = it.accessToken, refreshToken = it.refreshToken)
                    }
                }
            }
        }
    }

    private fun HttpClientConfig<*>.installContentNegotiation() {
        install(ContentNegotiation) {
            json()
        }
    }

    private fun HttpClientConfig<*>.installLogging() {
        install(Logging) {
            logger = Logger.SIMPLE
            level = LogLevel.ALL
        }
    }

    override suspend fun getMe(): Profile {
        return this.client.get("/api/users/me").body()
    }

    // And so on
}

不幸的是,httpclient crastes crastes https发送请求。到具有自签名证书的服务器:

iOS上的Android上:

I/System.out: HttpClient: REQUEST https://xxx.xxx.xxx.xxx:xxx/api/oauth2/token failed with exception: javax.net.ssl.SSLHandshakeException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.

Uncaught Kotlin exception: io.ktor.client.engine.darwin.DarwinHttpRequestException: Exception in http request: Error Domain=NSURLErrorDomain Code=-1202 "The certificate for this server is invalid. You might be connecting to a server that is pretending to be “xxx.xxx.xxx.xxx” which could put your confidential information at risk." UserInfo={NSLocalizedRecoverySuggestion=Would you like to connect to the server anyway?, _kCFStreamErrorDomainKey=3, NSErrorPeerCertificateChainKey=(
    "<cert(0x15704bc00) s: xxx.xxx.xxx.xxx i: xxx.xxx.xxx.xxx>"

我如何设置KTOR客户端以信任自签名证书?

I have a server with self-signed certificate and without domain name (just static IP address), this server provides API for Android and iOS clients only, so IP and self-signed certificate is enough for my purposes.

The mobile client is written on KMM and it uses Ktor client to perform requests:

ApiGateway.kt:

class ApiGateway(
    private val rootUrl: String,
    private val clientId: String,
    private val secretKey: String,
    private val accountManager: IAccountManagerGateway,
    private val getDeviceName: GetDeviceNameUseCase,
) : IApiGateway {
    private val client = HttpClient {
        installContentNegotiation()
        installLogging()
        configureRequest()
        configureResponseValidator()
        installAuth()
    }
    private var account: Account? = this.accountManager.getAccount()

    private fun HttpClientConfig<*>.configureRequest() {
        defaultRequest {
            url([email protected])
            accept(ContentType.Application.Json)
            contentType(ContentType.Application.Json)
        }
    }

    private fun HttpClientConfig<*>.configureResponseValidator() {
        HttpResponseValidator {
            validateResponse { // Stub
                when (it.status.value) {
                    in 300..399 -> throw ClientException(it.bodyAsText())
                    in 400..499 -> throw ClientException(it.bodyAsText())
                    in 500..599 -> throw ClientException(it.bodyAsText())
                }
            }
        }
    }

    private fun HttpClientConfig<*>.installAuth() {
        install(Auth) {
            bearer {
                sendWithoutRequest { request ->
                    val url = request.url.build().toString()
                    !(
                            url.contains("/api/oauth2/token")
                                    || (url.contains("/api/users") &&
                                            request.method == HttpMethod.Post
                                    )
                    )
                }
                loadTokens {
                    [email protected]?.let {
                        BearerTokens(accessToken = it.accessToken, refreshToken = it.refreshToken)
                    }
                }
                refreshTokens {
                    [email protected]?.let {
                        [email protected]()
                        BearerTokens(accessToken = it.accessToken, refreshToken = it.refreshToken)
                    }
                }
            }
        }
    }

    private fun HttpClientConfig<*>.installContentNegotiation() {
        install(ContentNegotiation) {
            json()
        }
    }

    private fun HttpClientConfig<*>.installLogging() {
        install(Logging) {
            logger = Logger.SIMPLE
            level = LogLevel.ALL
        }
    }

    override suspend fun getMe(): Profile {
        return this.client.get("/api/users/me").body()
    }

    // And so on
}

Unfortunately, HttpClient crashes sending a request by HTTPS to a server with self-signed certificate:

On Android:

I/System.out: HttpClient: REQUEST https://xxx.xxx.xxx.xxx:xxx/api/oauth2/token failed with exception: javax.net.ssl.SSLHandshakeException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.

On iOS:

Uncaught Kotlin exception: io.ktor.client.engine.darwin.DarwinHttpRequestException: Exception in http request: Error Domain=NSURLErrorDomain Code=-1202 "The certificate for this server is invalid. You might be connecting to a server that is pretending to be “xxx.xxx.xxx.xxx” which could put your confidential information at risk." UserInfo={NSLocalizedRecoverySuggestion=Would you like to connect to the server anyway?, _kCFStreamErrorDomainKey=3, NSErrorPeerCertificateChainKey=(
    "<cert(0x15704bc00) s: xxx.xxx.xxx.xxx i: xxx.xxx.xxx.xxx>"

How can I setup the Ktor client to trust a self-signed certificate?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文