From 1e9380f51c0efc2a90c7a17f17b5811103aab4d5 Mon Sep 17 00:00:00 2001 From: Trong Huu Nguyen Date: Wed, 20 Sep 2023 14:45:48 +0200 Subject: [PATCH] fix(config): retry request failures when fetching well-known documents --- .../nais/security/oauth2/TokenExchangeApp.kt | 21 ++++++++++++++++++- .../oauth2/config/AppConfiguration.kt | 6 +++--- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/src/main/kotlin/io/nais/security/oauth2/TokenExchangeApp.kt b/src/main/kotlin/io/nais/security/oauth2/TokenExchangeApp.kt index 37f0d523..5d1dc929 100644 --- a/src/main/kotlin/io/nais/security/oauth2/TokenExchangeApp.kt +++ b/src/main/kotlin/io/nais/security/oauth2/TokenExchangeApp.kt @@ -7,6 +7,7 @@ import com.nimbusds.oauth2.sdk.ErrorObject import com.nimbusds.oauth2.sdk.OAuth2Error import io.ktor.client.HttpClient import io.ktor.client.engine.cio.CIO +import io.ktor.client.plugins.HttpRequestRetry import io.ktor.http.HttpHeaders import io.ktor.http.HttpStatusCode import io.ktor.serialization.jackson.jackson @@ -57,9 +58,11 @@ import org.slf4j.event.Level import java.util.UUID import java.util.concurrent.TimeUnit.SECONDS import kotlin.system.exitProcess +import io.ktor.client.plugins.contentnegotiation.ContentNegotiation as ClientContentNegotiation private val log = KotlinLogging.logger { } +const val httpClientMaxRetries = 10 const val shutdownGracePeriod = 10L const val shutdownMaxWait = 20L @@ -141,12 +144,15 @@ fun Application.tokenExchangeApp(config: AppConfiguration, routing: ApiRouting) val includeErrorDetails = isNonProd() call.respondWithError(cause, includeErrorDetails) } + is BadRequestException -> { call.respond(HttpStatusCode.BadRequest, "invalid request content") } + is JsonProcessingException -> { call.respond(HttpStatusCode.BadRequest, "invalid request content") } + else -> { call.respond(HttpStatusCode.InternalServerError, "unknown internal server error") } @@ -195,10 +201,23 @@ private fun ErrorObject.toGeneric(): ErrorObject = ) internal val defaultHttpClient = HttpClient(CIO) { - install(io.ktor.client.plugins.contentnegotiation.ContentNegotiation) { + install(ClientContentNegotiation) { jackson() { setSerializationInclusion(NON_NULL) configure(FAIL_ON_UNKNOWN_PROPERTIES, false) } } } + +internal val retryingHttpClient = HttpClient(CIO) { + install(ClientContentNegotiation) { + jackson() { + setSerializationInclusion(NON_NULL) + configure(FAIL_ON_UNKNOWN_PROPERTIES, false) + } + } + install(HttpRequestRetry) { + retryOnExceptionOrServerErrors(maxRetries = httpClientMaxRetries) + exponentialDelay() + } +} diff --git a/src/main/kotlin/io/nais/security/oauth2/config/AppConfiguration.kt b/src/main/kotlin/io/nais/security/oauth2/config/AppConfiguration.kt index fd6fbca7..5466881c 100644 --- a/src/main/kotlin/io/nais/security/oauth2/config/AppConfiguration.kt +++ b/src/main/kotlin/io/nais/security/oauth2/config/AppConfiguration.kt @@ -13,7 +13,6 @@ import io.nais.security.oauth2.authentication.BearerTokenAuth import io.nais.security.oauth2.config.JwkCache.BUCKET_SIZE import io.nais.security.oauth2.config.JwkCache.CACHE_SIZE import io.nais.security.oauth2.config.JwkCache.EXPIRES_IN -import io.nais.security.oauth2.defaultHttpClient import io.nais.security.oauth2.health.DatabaseHealthCheck import io.nais.security.oauth2.health.HealthCheck import io.nais.security.oauth2.keystore.RotatingKeyStore @@ -23,6 +22,7 @@ import io.nais.security.oauth2.model.ClaimMappings import io.nais.security.oauth2.model.WellKnown import io.nais.security.oauth2.registration.ClientRegistry import io.nais.security.oauth2.registration.ClientRegistryPostgres +import io.nais.security.oauth2.retryingHttpClient import io.nais.security.oauth2.token.TokenIssuer import kotlinx.coroutines.runBlocking import mu.KotlinLogging @@ -85,7 +85,7 @@ class AuthProvider( fun fromWellKnown(wellKnownUrl: String): AuthProvider { val wellKnown: WellKnown = runBlocking { log.info("getting OpenID Connect server metadata from well-known url=$wellKnownUrl") - defaultHttpClient.get(wellKnownUrl).body() + retryingHttpClient.get(wellKnownUrl).body() } val jwk = JwkProviderBuilder(URL(wellKnown.jwksUri)) .cached(CACHE_SIZE, EXPIRES_IN, TimeUnit.HOURS) @@ -130,7 +130,7 @@ class AuthorizationServerProperties( class SubjectTokenIssuer(private val wellKnownUrl: String, val subjectTokenClaimMappings: ClaimMappings = emptyMap()) { val wellKnown: WellKnown = runBlocking { log.info("getting OAuth2 server metadata from well-known url=$wellKnownUrl") - defaultHttpClient.get(wellKnownUrl).body() + retryingHttpClient.get(wellKnownUrl).body() } val issuer = wellKnown.issuer val cacheProperties = CacheProperties(