У меня возникли проблемы с Ktor в приложении Kotlin, которое я пишу. Я следовал инструкциям здесь https://ktor.io/docs/client-bearer-auth ... uth-google для того, что, как я полагаю, является довольно стандартным потоком аутентификации, только измененным таким образом, чтобы он аутентифицировался на сервере keycloak. и Ktor HttpClient внедряется в другой мой класс обработчика API, например. ApiClient, используя Hilt.
Вход в мое приложение работает следующим образом: пользователь входит в систему с помощью ApiClient.authenticate(), который устанавливает мои токены. Все это кажется действительным. Затем приложение немедленно авторизует пользователя, получая его роли с помощью ApiClient.authorize(), для чего требуется токен доступа для аутентификации носителя, который мы только что установили на предыдущем шаге. Кажется, это тоже верно.
Оказавшись в приложении, пользователь может открыть действие Foo, модель представления которого FooViewModel вызывает ApiClient.getFoo() при инициализации. Это работает нормально, если не прошло более 10 минут, когда я получаю io.ktor.client.call.NoTransformationFoundException со статусом ответа 401. Единственная причина, по которой это имеет смысл, заключается в том, что Kotlin явно пытается десериализовать ответ 401 в объект List, но я не понимаю, почему он взрывается при ответе 401 в первом место.
Как вы можете видеть в DIModules.kt, мой ApiClient настроен для внедрения с помощью Ktor HttpClient, и этот HttpClient настроен на автоматическое обновление токена доступа. Насколько я могу судить, в моем приложении такого никогда не происходило.
Может ли кто-нибудь, знающий об этих методах аутентификации, помочь мне? Я чувствую себя немного потерянным, продолжая читать и перечитывать документацию Ktor, и мне до сих пор не ясно, где моя ошибка.
Для большего контекста этот сервер keycloak не установлен вплоть до требования секрета клиента. Срок действия токенов доступа истекает через 300 с, а срок действия токенов обновления истекает через 1800 с.
ApiClient.kt
class ApiClient @Inject constructor(
val prefs: PrefsManager,
val httpClient: HttpClient
) {
// initializing our tokens
suspend fun authenticate(username: String, password: String) : TokenInfo {
try {
val res : TokenInfo = httpClient.submitForm(
url = "${prefs.siteURL}${prefs.authRoute}/token",
parameters {
append("client_id", prefs.clientId!!)
append("username", username)
append("password", password)
append("grant_type", "password")
append("scope", "openid")
}
).body()
TokenStorage.tokens.add(BearerTokens(res.accessToken, res.refreshToken ?: ""))
return res
} catch (e: Throwable) {
throw HttpClientException("Authentication Error: ${e.message ?: "Unexpected error occurred"}")
}
}
// getting a protected resource requiring a bearer token
suspend fun authorize() : RoleResponse {
val res = httpClient.get("${prefs.siteURL}${prefs.authRoute}/userinfo?")
try {
return res.body()
} catch (e: Throwable) {
throw HttpClientException("Authorization Error: ${e.message ?: "Unexpected error occurred"}")
}
}
// getting another protected resource requiring a bearer token
suspend fun fetchFoo(): List {
return httpClient.get("${prefs.siteURL}/fooendpoint").body()
}
}
FooViewModel.kt
init {
try {
isLoading = true
apiClient.fetchFoo()
} catch (e: Exception) {
// handle exception
} finally {
isLoading = false
}
}
DIModules.kt
@Module
@InstallIn(SingletonComponent::class)
object ApiClientModule {
@Provides
@Singleton
fun provideApiClient(prefs: PrefsManager) : ApiClient {
val httpClient = HttpClient(CIO) {
expectSuccess = false
install(ContentNegotiation) {
json(Json{
ignoreUnknownKeys = true
isLenient = true
})
}
install(HttpTimeout) {
requestTimeoutMillis = 8000
}
install(Auth) {
bearer {
loadTokens {
TokenStorage.tokens.last()
}
sendWithoutRequest { request ->
request.url.host == "${prefs.siteURL}${prefs.authRoute}"
}
refreshTokens {
val refreshTokenInfo: TokenInfo = client.submitForm(
url = "${prefs.siteURL}${prefs.authRoute}/token",
formParameters = parameters {
append("grant_type", "refresh_token")
append("client_id", prefs.clientId!!)
append("refresh_token", oldTokens?.refreshToken ?: "")
}
) { markAsRefreshTokenRequest() }.body()
TokenStorage.tokens.add(BearerTokens(refreshTokenInfo.accessToken, oldTokens?.refreshToken!!))
TokenStorage.tokens.last()
}
}
}
}
return ApiClient(prefs, httpClient)
}
}
TokenStorage.kt
object TokenStorage {
val tokens = mutableListOf()
}
TokenInfo.kt
@Serializable
data class TokenInfo(
@SerialName("access_token") val accessToken: String,
@SerialName("expires_in") val expiresIn: Int,
@SerialName("refresh_token") val refreshToken: String? = null,
val scope: String,
@SerialName("token_type") val tokenType: String,
@SerialName("id_token") val idToken: String,
)
Подробнее здесь: https://stackoverflow.com/questions/790 ... ing-on-401
Ktor HttpClient RefreshTokens не срабатывает при ошибке 401 ⇐ Android
Форум для тех, кто программирует под Android
-
Anonymous
1727373680
Anonymous
У меня возникли проблемы с Ktor в приложении Kotlin, которое я пишу. Я следовал инструкциям здесь https://ktor.io/docs/client-bearer-auth.html#example-oauth-google для того, что, как я полагаю, является довольно стандартным потоком аутентификации, только измененным таким образом, чтобы он аутентифицировался на сервере keycloak. и Ktor HttpClient внедряется в другой мой класс обработчика API, например. ApiClient, используя Hilt.
Вход в мое приложение работает следующим образом: пользователь входит в систему с помощью ApiClient.authenticate(), который устанавливает мои токены. Все это кажется действительным. Затем приложение немедленно авторизует пользователя, получая его роли с помощью ApiClient.authorize(), для чего требуется токен доступа для аутентификации носителя, который мы только что установили на предыдущем шаге. Кажется, это тоже верно.
Оказавшись в приложении, пользователь может открыть действие Foo, модель представления которого FooViewModel вызывает ApiClient.getFoo() при инициализации. Это работает нормально, если не прошло более 10 минут, когда я получаю io.ktor.client.call.NoTransformationFoundException со статусом ответа 401. Единственная причина, по которой это имеет смысл, заключается в том, что Kotlin явно пытается десериализовать ответ 401 в объект List, но я не понимаю, почему он взрывается при ответе 401 в первом место.
Как вы можете видеть в DIModules.kt, мой ApiClient настроен для внедрения с помощью Ktor HttpClient, и этот HttpClient настроен на автоматическое обновление токена доступа. Насколько я могу судить, в моем приложении такого никогда не происходило.
Может ли кто-нибудь, знающий об этих методах аутентификации, помочь мне? Я чувствую себя немного потерянным, продолжая читать и перечитывать документацию Ktor, и мне до сих пор не ясно, где моя ошибка.
Для большего контекста этот сервер keycloak не установлен вплоть до требования секрета клиента. Срок действия токенов доступа истекает через 300 с, а срок действия токенов обновления истекает через 1800 с.
ApiClient.kt
class ApiClient @Inject constructor(
val prefs: PrefsManager,
val httpClient: HttpClient
) {
// initializing our tokens
suspend fun authenticate(username: String, password: String) : TokenInfo {
try {
val res : TokenInfo = httpClient.submitForm(
url = "${prefs.siteURL}${prefs.authRoute}/token",
parameters {
append("client_id", prefs.clientId!!)
append("username", username)
append("password", password)
append("grant_type", "password")
append("scope", "openid")
}
).body()
TokenStorage.tokens.add(BearerTokens(res.accessToken, res.refreshToken ?: ""))
return res
} catch (e: Throwable) {
throw HttpClientException("Authentication Error: ${e.message ?: "Unexpected error occurred"}")
}
}
// getting a protected resource requiring a bearer token
suspend fun authorize() : RoleResponse {
val res = httpClient.get("${prefs.siteURL}${prefs.authRoute}/userinfo?")
try {
return res.body()
} catch (e: Throwable) {
throw HttpClientException("Authorization Error: ${e.message ?: "Unexpected error occurred"}")
}
}
// getting another protected resource requiring a bearer token
suspend fun fetchFoo(): List {
return httpClient.get("${prefs.siteURL}/fooendpoint").body()
}
}
FooViewModel.kt
init {
try {
isLoading = true
apiClient.fetchFoo()
} catch (e: Exception) {
// handle exception
} finally {
isLoading = false
}
}
DIModules.kt
@Module
@InstallIn(SingletonComponent::class)
object ApiClientModule {
@Provides
@Singleton
fun provideApiClient(prefs: PrefsManager) : ApiClient {
val httpClient = HttpClient(CIO) {
expectSuccess = false
install(ContentNegotiation) {
json(Json{
ignoreUnknownKeys = true
isLenient = true
})
}
install(HttpTimeout) {
requestTimeoutMillis = 8000
}
install(Auth) {
bearer {
loadTokens {
TokenStorage.tokens.last()
}
sendWithoutRequest { request ->
request.url.host == "${prefs.siteURL}${prefs.authRoute}"
}
refreshTokens {
val refreshTokenInfo: TokenInfo = client.submitForm(
url = "${prefs.siteURL}${prefs.authRoute}/token",
formParameters = parameters {
append("grant_type", "refresh_token")
append("client_id", prefs.clientId!!)
append("refresh_token", oldTokens?.refreshToken ?: "")
}
) { markAsRefreshTokenRequest() }.body()
TokenStorage.tokens.add(BearerTokens(refreshTokenInfo.accessToken, oldTokens?.refreshToken!!))
TokenStorage.tokens.last()
}
}
}
}
return ApiClient(prefs, httpClient)
}
}
TokenStorage.kt
object TokenStorage {
val tokens = mutableListOf()
}
TokenInfo.kt
@Serializable
data class TokenInfo(
@SerialName("access_token") val accessToken: String,
@SerialName("expires_in") val expiresIn: Int,
@SerialName("refresh_token") val refreshToken: String? = null,
val scope: String,
@SerialName("token_type") val tokenType: String,
@SerialName("id_token") val idToken: String,
)
Подробнее здесь: [url]https://stackoverflow.com/questions/79028613/ktor-httpclient-refreshtokens-not-firing-on-401[/url]
Ответить
1 сообщение
• Страница 1 из 1
Перейти
- Кемерово-IT
- ↳ Javascript
- ↳ C#
- ↳ JAVA
- ↳ Elasticsearch aggregation
- ↳ Python
- ↳ Php
- ↳ Android
- ↳ Html
- ↳ Jquery
- ↳ C++
- ↳ IOS
- ↳ CSS
- ↳ Excel
- ↳ Linux
- ↳ Apache
- ↳ MySql
- Детский мир
- Для души
- ↳ Музыкальные инструменты даром
- ↳ Печатная продукция даром
- Внешняя красота и здоровье
- ↳ Одежда и обувь для взрослых даром
- ↳ Товары для здоровья
- ↳ Физкультура и спорт
- Техника - даром!
- ↳ Автомобилистам
- ↳ Компьютерная техника
- ↳ Плиты: газовые и электрические
- ↳ Холодильники
- ↳ Стиральные машины
- ↳ Телевизоры
- ↳ Телефоны, смартфоны, плашеты
- ↳ Швейные машинки
- ↳ Прочая электроника и техника
- ↳ Фототехника
- Ремонт и интерьер
- ↳ Стройматериалы, инструмент
- ↳ Мебель и предметы интерьера даром
- ↳ Cантехника
- Другие темы
- ↳ Разное даром
- ↳ Давай меняться!
- ↳ Отдам\возьму за копеечку
- ↳ Работа и подработка в Кемерове
- ↳ Давай с тобой поговорим...
Мобильная версия