diff --git a/ktor-auth-maskinporten/main/no/nav/aap/ktor/client/maskinporten/client/HttpClientMaskinportenTokenProvider.kt b/ktor-auth-maskinporten/main/no/nav/aap/ktor/client/maskinporten/client/HttpClientMaskinportenTokenProvider.kt index bbcbd308..dfd9fd1f 100644 --- a/ktor-auth-maskinporten/main/no/nav/aap/ktor/client/maskinporten/client/HttpClientMaskinportenTokenProvider.kt +++ b/ktor-auth-maskinporten/main/no/nav/aap/ktor/client/maskinporten/client/HttpClientMaskinportenTokenProvider.kt @@ -11,7 +11,6 @@ import io.ktor.client.request.* import io.ktor.http.* import io.ktor.http.ContentType.Application.FormUrlEncoded import io.ktor.serialization.jackson.* -import java.time.Instant const val GRANT_TYPE = "urn:ietf:params:oauth:grant-type:jwt-bearer" const val LEEWAY_SECONDS: Long = 20 @@ -22,20 +21,13 @@ interface Oauth2JwtProvider { class HttpClientMaskinportenTokenProvider( private val config: MaskinportenConfig, + private val client: HttpClient = defaultHttpClient ) : Oauth2JwtProvider { private val grants: JwtGrantFactory = JwtGrantFactory(config) - private val cache = mutableMapOf() - private val client = HttpClient(CIO) { - install(ContentNegotiation) { - jackson { - registerModule(JavaTimeModule()) - disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) - } - } - } + private val cache = TokenCache() override suspend fun getToken(): String { - val token = cache[config.scope]?.takeUnless(Token::hasExpired) ?: fetchToken() + val token = cache.get(config.scope)?.takeUnless(Token::hasExpired) ?: fetchToken() return token.access_token.let(SignedJWT::parse).parsedString } @@ -44,13 +36,17 @@ class HttpClientMaskinportenTokenProvider( contentType(FormUrlEncoded) setBody("grant_type=$GRANT_TYPE&assertion=${grants.jwt}") }.body().also { token -> - cache[config.scope] = token + cache.add(config.scope, token) } - private data class Token(val access_token: String) { - private val signedJwt = SignedJWT.parse(access_token) - private val expiry = signedJwt.jwtClaimsSet.expirationTime.toInstant().minusSeconds(LEEWAY_SECONDS) - - fun hasExpired() = expiry < Instant.now() + private companion object { + private val defaultHttpClient = HttpClient(CIO) { + install(ContentNegotiation) { + jackson { + registerModule(JavaTimeModule()) + disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) + } + } + } } } diff --git a/ktor-auth-maskinporten/main/no/nav/aap/ktor/client/maskinporten/client/Token.kt b/ktor-auth-maskinporten/main/no/nav/aap/ktor/client/maskinporten/client/Token.kt new file mode 100644 index 00000000..3fbd2186 --- /dev/null +++ b/ktor-auth-maskinporten/main/no/nav/aap/ktor/client/maskinporten/client/Token.kt @@ -0,0 +1,51 @@ +package no.nav.aap.ktor.client.maskinporten.client + +import com.nimbusds.jwt.SignedJWT +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock +import org.slf4j.Logger +import java.time.Instant + +internal data class Token(val access_token: String) { + private val signedJwt = SignedJWT.parse(access_token) + private val expiry = signedJwt.jwtClaimsSet.expirationTime.toInstant().minusSeconds(LEEWAY_SECONDS) + + fun hasExpired() = expiry < Instant.now() +} + +internal class TokenCache { + private val tokens: HashMap = hashMapOf() + private val mutex = Mutex() + + internal fun logg(logger: Logger) { + tokens.forEach { (key, value) -> + logger.info("Key: $key, Value: $value") + } + } + + internal suspend fun add(key: String, token: Token) { + mutex.withLock { + tokens[key] = token + } + } + + internal suspend fun get(key: String): Token? { + mutex.withLock { + tokens[key] + }?.let { + if (it.hasExpired()) { + rm(key) + } + } + + return mutex.withLock { + tokens[key] + } + } + + private suspend fun rm(key: String) { + mutex.withLock { + tokens.remove(key) + } + } +} \ No newline at end of file