Ktor 客户端 Auth 功能不发送授权 header
Ktor client Auth feature does not sending Authorization header
我正在尝试使用ktorclient in Kotlin/MPP (Multiplatform) project and on JVM target feature basic authentication好像没有效果
这里有一个重现的例子:
import io.ktor.client.HttpClient
import io.ktor.client.features.ResponseException
import io.ktor.client.features.auth.Auth
import io.ktor.client.features.auth.providers.basic
import io.ktor.client.features.json.JsonFeature
import io.ktor.client.features.json.serializer.KotlinxSerializer
import io.ktor.client.features.logging.DEFAULT
import io.ktor.client.features.logging.LogLevel
import io.ktor.client.features.logging.Logger
import io.ktor.client.features.logging.Logging
import io.ktor.client.request.get
import io.ktor.client.request.header
import kotlinx.coroutines.runBlocking
import java.util.*
fun main() = runBlocking {
val client = HttpClient {
install(Logging) {
logger = Logger.DEFAULT
level = LogLevel.HEADERS
}
install(JsonFeature) {
serializer = KotlinxSerializer()
}
install(Auth) {
basic {
username = "user"
password = "pass"
}
}
}
val url = "https://en.wikipedia.org/wiki/Main_Page"
val failing = try {
client.get<String>(url)
} catch (e: ResponseException) {
"failed"
}
val succeeding = try {
client.get<String>(url) {
header("Authorization", "Basic ${Base64.getEncoder().encodeToString("user:pass".toByteArray())}")
}
} catch (e: ResponseException) {
"failed"
}
}
观察
从记录器输出中,您可以看到客户端没有发送 Authorization
header 但是当我手动提供这样的 header 时我没有遇到任何问题:
第一个请求(失败示例:)
[main] INFO io.ktor.client.HttpClient - REQUEST: https://en.wikipedia.org/wiki/Main_Page
[main] INFO io.ktor.client.HttpClient - METHOD: HttpMethod(value=GET)
[main] INFO io.ktor.client.HttpClient - COMMON HEADERS
[main] INFO io.ktor.client.HttpClient - -> Accept: application/json
[main] INFO io.ktor.client.HttpClient - -> Accept-Charset: UTF-8
[main] INFO io.ktor.client.HttpClient - CONTENT HEADERS
第二次请求(后续示例:)
[main] INFO io.ktor.client.HttpClient - REQUEST: https://en.wikipedia.org/wiki/Main_Page
[main] INFO io.ktor.client.HttpClient - METHOD: HttpMethod(value=GET)
[main] INFO io.ktor.client.HttpClient - COMMON HEADERS
[main] INFO io.ktor.client.HttpClient - -> Authorization: Basic dXNlcjpwYXNz
[main] INFO io.ktor.client.HttpClient - -> Accept: application/json
[main] INFO io.ktor.client.HttpClient - -> Accept-Charset: UTF-8
[main] INFO io.ktor.client.HttpClient - CONTENT HEADERS
环境
- 科特林:1.4-M1
Ktor 工件版本 1.3.1:
- ktor-client-core
- ktor-client-logging
- ktor-client-json
- ktor-client-serialization
- ktor-client-auth-basic
我是不是漏掉了什么?
请添加sendWithoutRequest = true
install(Auth) {
basic {
sendWithoutRequest = true
username = "user"
password = "pass"
}
}
结果:
sending with sendWithoutRequest set to true
[main] INFO io.ktor.client.HttpClient - REQUEST: https://en.wikipedia.org/wiki/Main_Page
[main] INFO io.ktor.client.HttpClient - METHOD: HttpMethod(value=GET)
[main] INFO io.ktor.client.HttpClient - COMMON HEADERS
[main] INFO io.ktor.client.HttpClient - -> Authorization: Basic dXNlcjpwYXNz
[main] INFO io.ktor.client.HttpClient - -> Accept: application/json
[main] INFO io.ktor.client.HttpClient - -> Accept-Charset: UTF-8
[main] INFO io.ktor.client.HttpClient - CONTENT HEADERS
解释:
By default, Ktor will wait for the server to respond with 401,
Unauthorized, and only then send the authentication header. In your
example, wiki never responds with a 401, as it is not a protected
resource. Therefore, adding sendWithoutRequest is required. If you
tried with some url that does respond with a 401, you would see that
Ktor will then send a second request (after receiving 401) with the
authentication header. You can try with this url to see -
https://api.sumologic.com/api/v1/collectors.
这是在关闭 sendWithoutRequest 的情况下针对受保护的 api 完成的日志记录,您的原始输入。如您所见,现在有 2 个请求,第一个没有授权 header,然后第二个有授权 header,在服务器响应 401 之后。
sending with sendWithoutRequest set to false and hitting a protected resource
[main] INFO io.ktor.client.HttpClient - REQUEST: https://api.sumologic.com/api/v1/collectors
[main] INFO io.ktor.client.HttpClient - METHOD: HttpMethod(value=GET)
[main] INFO io.ktor.client.HttpClient - COMMON HEADERS
[main] INFO io.ktor.client.HttpClient - -> Accept: application/json
[main] INFO io.ktor.client.HttpClient - -> Accept-Charset: UTF-8
[main] INFO io.ktor.client.HttpClient - CONTENT HEADERS
[main] INFO io.ktor.client.HttpClient - REQUEST: https://api.sumologic.com/api/v1/collectors
[main] INFO io.ktor.client.HttpClient - METHOD: HttpMethod(value=GET)
[main] INFO io.ktor.client.HttpClient - COMMON HEADERS
[main] INFO io.ktor.client.HttpClient - -> Accept: application/json
[main] INFO io.ktor.client.HttpClient - -> Accept-Charset: UTF-8
[main] INFO io.ktor.client.HttpClient - -> Authorization: Basic dXNlcjpwYXNz
[main] INFO io.ktor.client.HttpClient - CONTENT HEADERS
注:刚刚看到Andylamax的评论说要出新版本"fixes"了。也许,我不知道,因为我还没有尝试过那个新版本。但我想补充一点,这不是 Ktor 独有的东西,至少在这方面不是 bug(但也许他们改变了主意?同样,我不知道)。事实上,正是我使用 C# 的经验让我怀疑这里发生了什么并找到了答案。 C# 中的 WebRequest 的行为方式相同,您需要将 PreAuthenticate 设置为 true 以立即发送凭据。请参阅此处 https://docs.microsoft.com/en-us/dotnet/api/system.net.webrequest.preauthenticate?view=netcore-3.1。
我正在尝试使用ktorclient in Kotlin/MPP (Multiplatform) project and on JVM target feature basic authentication好像没有效果
这里有一个重现的例子:
import io.ktor.client.HttpClient
import io.ktor.client.features.ResponseException
import io.ktor.client.features.auth.Auth
import io.ktor.client.features.auth.providers.basic
import io.ktor.client.features.json.JsonFeature
import io.ktor.client.features.json.serializer.KotlinxSerializer
import io.ktor.client.features.logging.DEFAULT
import io.ktor.client.features.logging.LogLevel
import io.ktor.client.features.logging.Logger
import io.ktor.client.features.logging.Logging
import io.ktor.client.request.get
import io.ktor.client.request.header
import kotlinx.coroutines.runBlocking
import java.util.*
fun main() = runBlocking {
val client = HttpClient {
install(Logging) {
logger = Logger.DEFAULT
level = LogLevel.HEADERS
}
install(JsonFeature) {
serializer = KotlinxSerializer()
}
install(Auth) {
basic {
username = "user"
password = "pass"
}
}
}
val url = "https://en.wikipedia.org/wiki/Main_Page"
val failing = try {
client.get<String>(url)
} catch (e: ResponseException) {
"failed"
}
val succeeding = try {
client.get<String>(url) {
header("Authorization", "Basic ${Base64.getEncoder().encodeToString("user:pass".toByteArray())}")
}
} catch (e: ResponseException) {
"failed"
}
}
观察
从记录器输出中,您可以看到客户端没有发送 Authorization
header 但是当我手动提供这样的 header 时我没有遇到任何问题:
第一个请求(失败示例:)
[main] INFO io.ktor.client.HttpClient - REQUEST: https://en.wikipedia.org/wiki/Main_Page
[main] INFO io.ktor.client.HttpClient - METHOD: HttpMethod(value=GET)
[main] INFO io.ktor.client.HttpClient - COMMON HEADERS
[main] INFO io.ktor.client.HttpClient - -> Accept: application/json
[main] INFO io.ktor.client.HttpClient - -> Accept-Charset: UTF-8
[main] INFO io.ktor.client.HttpClient - CONTENT HEADERS
第二次请求(后续示例:)
[main] INFO io.ktor.client.HttpClient - REQUEST: https://en.wikipedia.org/wiki/Main_Page
[main] INFO io.ktor.client.HttpClient - METHOD: HttpMethod(value=GET)
[main] INFO io.ktor.client.HttpClient - COMMON HEADERS
[main] INFO io.ktor.client.HttpClient - -> Authorization: Basic dXNlcjpwYXNz
[main] INFO io.ktor.client.HttpClient - -> Accept: application/json
[main] INFO io.ktor.client.HttpClient - -> Accept-Charset: UTF-8
[main] INFO io.ktor.client.HttpClient - CONTENT HEADERS
环境
- 科特林:1.4-M1
Ktor 工件版本 1.3.1:
- ktor-client-core
- ktor-client-logging
- ktor-client-json
- ktor-client-serialization
- ktor-client-auth-basic
我是不是漏掉了什么?
请添加sendWithoutRequest = true
install(Auth) {
basic {
sendWithoutRequest = true
username = "user"
password = "pass"
}
}
结果:
sending with sendWithoutRequest set to true
[main] INFO io.ktor.client.HttpClient - REQUEST: https://en.wikipedia.org/wiki/Main_Page
[main] INFO io.ktor.client.HttpClient - METHOD: HttpMethod(value=GET)
[main] INFO io.ktor.client.HttpClient - COMMON HEADERS
[main] INFO io.ktor.client.HttpClient - -> Authorization: Basic dXNlcjpwYXNz
[main] INFO io.ktor.client.HttpClient - -> Accept: application/json
[main] INFO io.ktor.client.HttpClient - -> Accept-Charset: UTF-8
[main] INFO io.ktor.client.HttpClient - CONTENT HEADERS
解释:
By default, Ktor will wait for the server to respond with 401, Unauthorized, and only then send the authentication header. In your example, wiki never responds with a 401, as it is not a protected resource. Therefore, adding sendWithoutRequest is required. If you tried with some url that does respond with a 401, you would see that Ktor will then send a second request (after receiving 401) with the authentication header. You can try with this url to see - https://api.sumologic.com/api/v1/collectors.
这是在关闭 sendWithoutRequest 的情况下针对受保护的 api 完成的日志记录,您的原始输入。如您所见,现在有 2 个请求,第一个没有授权 header,然后第二个有授权 header,在服务器响应 401 之后。
sending with sendWithoutRequest set to false and hitting a protected resource
[main] INFO io.ktor.client.HttpClient - REQUEST: https://api.sumologic.com/api/v1/collectors
[main] INFO io.ktor.client.HttpClient - METHOD: HttpMethod(value=GET)
[main] INFO io.ktor.client.HttpClient - COMMON HEADERS
[main] INFO io.ktor.client.HttpClient - -> Accept: application/json
[main] INFO io.ktor.client.HttpClient - -> Accept-Charset: UTF-8
[main] INFO io.ktor.client.HttpClient - CONTENT HEADERS
[main] INFO io.ktor.client.HttpClient - REQUEST: https://api.sumologic.com/api/v1/collectors
[main] INFO io.ktor.client.HttpClient - METHOD: HttpMethod(value=GET)
[main] INFO io.ktor.client.HttpClient - COMMON HEADERS
[main] INFO io.ktor.client.HttpClient - -> Accept: application/json
[main] INFO io.ktor.client.HttpClient - -> Accept-Charset: UTF-8
[main] INFO io.ktor.client.HttpClient - -> Authorization: Basic dXNlcjpwYXNz
[main] INFO io.ktor.client.HttpClient - CONTENT HEADERS
注:刚刚看到Andylamax的评论说要出新版本"fixes"了。也许,我不知道,因为我还没有尝试过那个新版本。但我想补充一点,这不是 Ktor 独有的东西,至少在这方面不是 bug(但也许他们改变了主意?同样,我不知道)。事实上,正是我使用 C# 的经验让我怀疑这里发生了什么并找到了答案。 C# 中的 WebRequest 的行为方式相同,您需要将 PreAuthenticate 设置为 true 以立即发送凭据。请参阅此处 https://docs.microsoft.com/en-us/dotnet/api/system.net.webrequest.preauthenticate?view=netcore-3.1。