Функция аутентификации клиента Ktor не отправляет заголовок авторизации
Я пытаюсь использовать клиентktor в проекте Kotlin/MPP (мультиплатформенный), а на целевой функции JVM базовая аутентификация, похоже, не оказывает никакого эффекта.
Вот пример для воспроизведения:
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, но у меня не возникает проблем, когда я задаю такой заголовок вручную:
Первый запрос (неудачный пример:)
[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-М1
Ktor Artifacts версии 1.3.1:
- ktor-client-core
- ktor-client-logging
- ktor-client-json
- ктор-клиент-сериализация
- ktor-client-auth-basic
Я что-то пропустил?
2 ответа
Пожалуйста, добавьте 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
Пояснение:
По умолчанию Ktor будет ждать ответа сервера 401, Unauthorized, и только после этого отправит заголовок аутентификации. В вашем примере wiki никогда не отвечает 401, поскольку это не защищенный ресурс. Следовательно, необходимо добавить sendWithoutRequest. Если вы попытаетесь использовать какой-либо URL-адрес, который отвечает 401, вы увидите, что Ktor отправит второй запрос (после получения 401) с заголовком аутентификации. Вы можете попробовать, используя этот URL-адрес - https://api.sumologic.com/api/v1/collectors.
Это ведение журнала, когда оно выполняется для этого защищенного API с отключенным sendWithoutRequest, вашим исходным вводом. Как видите, теперь сделано 2 запроса, первый без заголовка авторизации, а затем второй с заголовком авторизации после того, как сервер ответил 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 о том, что новая версия "исправляет" это. Возможно, я не знаю, как я не пробовал с той новой версией. Но я хотел бы добавить, что это не что-то уникальное для Ktor, и, по крайней мере, в этом отношении не является ошибкой (но, может быть, они передумали? Опять же, я не знаю). Фактически, именно мой опыт работы с C# заставил меня заподозрить, что здесь происходит, и найти ответ. WebRequest в C# ведет себя точно так же, вам нужно установить PreAuthenticate в значение true, чтобы немедленно отправить учетные данные. См. Здесь https://docs.microsoft.com/en-us/dotnet/api/system.net.webrequest.preauthenticate?view=netcore-3.1.
Ктор 2.1.0
import io.ktor.client.*
import io.ktor.client.call.*
import io.ktor.client.engine.cio.*
import io.ktor.client.plugins.auth.*
import io.ktor.client.plugins.auth.providers.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
private val httpClient = HttpClient(CIO) {
install(Auth) {
basic {
credentials {
BasicAuthCredentials(
username = "user",
password = "pass"
)
}
}
}
}
build.gradle
implementation("io.ktor:ktor-client-core:2.1.0")
implementation("io.ktor:ktor-client-cio:2.1.0")
implementation("io.ktor:ktor-client-auth:2.1.0")