diff --git a/build.gradle.kts b/build.gradle.kts index bf0d7cd..6815983 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -4,8 +4,8 @@ plugins { kotlin("jvm") version "1.9.22" `maven-publish` `java-library` - id("com.diffplug.spotless") version "6.23.3" - id("com.github.ben-manes.versions") version "0.50.0" + id("com.diffplug.spotless") version "6.25.0" + id("com.github.ben-manes.versions") version "0.51.0" id("se.patrikerdes.use-latest-versions") version "0.2.18" } @@ -44,11 +44,11 @@ subprojects { } dependencies { - implementation(platform("org.springframework.boot:spring-boot-dependencies:3.2.1")) + implementation(platform("org.springframework.boot:spring-boot-dependencies:3.2.2")) testImplementation("org.junit.jupiter:junit-jupiter") testImplementation("org.assertj:assertj-core") - testImplementation("io.mockk:mockk:1.13.8") + testImplementation("io.mockk:mockk:1.13.9") testImplementation("ch.qos.logback:logback-core") testImplementation("ch.qos.logback:logback-classic") diff --git a/http-client/build.gradle.kts b/http-client/build.gradle.kts index ede7c44..15f936a 100644 --- a/http-client/build.gradle.kts +++ b/http-client/build.gradle.kts @@ -1,6 +1,6 @@ -val tokenSupportVersion = "3.2.0" +val tokenSupportVersion = "4.1.3" val wiremockVersion = "3.0.1" -val tilleggsstønaderKontrakterVersion = "2023.12.18-10.13.7d6848ac9d82" +val tilleggsstønaderKontrakterVersion = "2024.02.07-10.15.07cb3f3164e4" plugins { kotlin("plugin.spring") version "1.9.22" diff --git a/http-client/main/no/nav/tilleggsstonader/libs/http/client/ProblemDetailException.kt b/http-client/main/no/nav/tilleggsstonader/libs/http/client/ProblemDetailException.kt index bc55344..08aade5 100644 --- a/http-client/main/no/nav/tilleggsstonader/libs/http/client/ProblemDetailException.kt +++ b/http-client/main/no/nav/tilleggsstonader/libs/http/client/ProblemDetailException.kt @@ -7,5 +7,5 @@ import org.springframework.web.client.RestClientResponseException class ProblemDetailException( val detail: ProblemDetail, val responseException: RestClientResponseException, - val httpStatus: HttpStatus = HttpStatus.valueOf(responseException.rawStatusCode), + val httpStatus: HttpStatus = HttpStatus.valueOf(responseException.statusCode.value()), ) : RuntimeException(responseException) diff --git a/http-client/main/no/nav/tilleggsstonader/libs/http/client/RetryOAuth2HttpClient.kt b/http-client/main/no/nav/tilleggsstonader/libs/http/client/RetryOAuth2HttpClient.kt index 5e27348..5fb507f 100644 --- a/http-client/main/no/nav/tilleggsstonader/libs/http/client/RetryOAuth2HttpClient.kt +++ b/http-client/main/no/nav/tilleggsstonader/libs/http/client/RetryOAuth2HttpClient.kt @@ -4,16 +4,16 @@ import no.nav.security.token.support.client.core.http.OAuth2HttpRequest import no.nav.security.token.support.client.core.oauth2.OAuth2AccessTokenResponse import no.nav.security.token.support.client.spring.oauth2.DefaultOAuth2HttpClient import org.slf4j.LoggerFactory -import org.springframework.boot.web.client.RestTemplateBuilder import org.springframework.core.NestedExceptionUtils import org.springframework.web.client.HttpServerErrorException +import org.springframework.web.client.RestClient import java.net.SocketException import java.net.SocketTimeoutException class RetryOAuth2HttpClient( - restTemplateBuilder: RestTemplateBuilder, + restClient: RestClient, private val maxRetries: Int = 2, -) : DefaultOAuth2HttpClient(restTemplateBuilder) { +) : DefaultOAuth2HttpClient(restClient) { private val logger = LoggerFactory.getLogger(javaClass) private val secureLogger = LoggerFactory.getLogger("secureLogger") @@ -23,10 +23,9 @@ class RetryOAuth2HttpClient( SocketException::class, SocketTimeoutException::class, HttpServerErrorException.GatewayTimeout::class, - HttpServerErrorException.BadGateway::class, ) - override fun post(req: OAuth2HttpRequest): OAuth2AccessTokenResponse? { + override fun post(req: OAuth2HttpRequest): OAuth2AccessTokenResponse { var retries = 0 while (true) { @@ -44,6 +43,7 @@ class RetryOAuth2HttpClient( retries: Int, oAuth2HttpRequest: OAuth2HttpRequest, ) { + e.printStackTrace() if (shouldRetry(e) && retries < maxRetries) { logger.warn( "Kall mot url=${oAuth2HttpRequest.tokenEndpointUrl} feilet, cause=${ diff --git a/http-client/main/no/nav/tilleggsstonader/libs/http/config/RestTemplateConfiguration.kt b/http-client/main/no/nav/tilleggsstonader/libs/http/config/RestTemplateConfiguration.kt index 340b503..752d1d7 100644 --- a/http-client/main/no/nav/tilleggsstonader/libs/http/config/RestTemplateConfiguration.kt +++ b/http-client/main/no/nav/tilleggsstonader/libs/http/config/RestTemplateConfiguration.kt @@ -7,11 +7,14 @@ import no.nav.tilleggsstonader.libs.http.interceptor.BearerTokenExchangeClientIn import no.nav.tilleggsstonader.libs.http.interceptor.BearerTokenOnBehalfOfClientInterceptor import no.nav.tilleggsstonader.libs.http.interceptor.ConsumerIdClientInterceptor import no.nav.tilleggsstonader.libs.http.interceptor.MdcValuesPropagatingClientInterceptor +import org.springframework.boot.web.client.ClientHttpRequestFactories +import org.springframework.boot.web.client.ClientHttpRequestFactorySettings import org.springframework.boot.web.client.RestTemplateBuilder import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration import org.springframework.context.annotation.Import import org.springframework.context.annotation.Primary +import org.springframework.web.client.RestClient import org.springframework.web.client.RestTemplate import java.time.Duration import java.time.temporal.ChronoUnit @@ -34,19 +37,19 @@ class RestTemplateConfiguration( @Primary @Bean fun oAuth2HttpClient( - restTemplateBuilder: RestTemplateBuilder, + restClientBuilder: RestClient.Builder, consumerIdClientInterceptor: ConsumerIdClientInterceptor, mdcValuesPropagatingClientInterceptor: MdcValuesPropagatingClientInterceptor, ): RetryOAuth2HttpClient { - return RetryOAuth2HttpClient( - restTemplateBuilder - .setConnectTimeout(Duration.of(2, ChronoUnit.SECONDS)) - .setReadTimeout(Duration.of(4, ChronoUnit.SECONDS)) - .additionalInterceptors( - consumerIdClientInterceptor, - mdcValuesPropagatingClientInterceptor, - ), - ) + val clientHttpRequestFactorySettings = ClientHttpRequestFactorySettings.DEFAULTS + .withConnectTimeout(Duration.of(1, ChronoUnit.SECONDS)) + .withReadTimeout(Duration.of(1, ChronoUnit.SECONDS)) + val restClient = restClientBuilder + .requestFactory(ClientHttpRequestFactories.get(clientHttpRequestFactorySettings)) + .requestInterceptor(consumerIdClientInterceptor) + .requestInterceptor(mdcValuesPropagatingClientInterceptor) + .build() + return RetryOAuth2HttpClient(restClient) } @Bean("utenAuth") diff --git a/http-client/main/no/nav/tilleggsstonader/libs/http/interceptor/BearerTokenClientInterceptor.kt b/http-client/main/no/nav/tilleggsstonader/libs/http/interceptor/BearerTokenClientInterceptor.kt index dbf3e51..dcfee38 100644 --- a/http-client/main/no/nav/tilleggsstonader/libs/http/interceptor/BearerTokenClientInterceptor.kt +++ b/http-client/main/no/nav/tilleggsstonader/libs/http/interceptor/BearerTokenClientInterceptor.kt @@ -1,9 +1,10 @@ package no.nav.tilleggsstonader.libs.http.interceptor +import com.nimbusds.oauth2.sdk.GrantType import no.nav.security.token.support.client.core.ClientProperties -import no.nav.security.token.support.client.core.OAuth2GrantType import no.nav.security.token.support.client.core.oauth2.OAuth2AccessTokenService import no.nav.security.token.support.client.spring.ClientConfigurationProperties +import no.nav.security.token.support.core.exceptions.JwtTokenMissingException import no.nav.security.token.support.spring.SpringTokenValidationContextHolder import org.springframework.http.HttpRequest import org.springframework.http.client.ClientHttpRequestExecution @@ -50,7 +51,7 @@ class BearerTokenClientCredentialsClientInterceptor( request, clientConfigurationProperties, oAuth2AccessTokenService, - OAuth2GrantType.CLIENT_CREDENTIALS, + GrantType.CLIENT_CREDENTIALS, ), ) return execution.execute(request, body) @@ -73,7 +74,7 @@ class BearerTokenExchangeClientInterceptor( request, clientConfigurationProperties, oAuth2AccessTokenService, - OAuth2GrantType.TOKEN_EXCHANGE, + GrantType.TOKEN_EXCHANGE, ), ) return execution.execute(request, body) @@ -96,7 +97,7 @@ class BearerTokenOnBehalfOfClientInterceptor( request, clientConfigurationProperties, oAuth2AccessTokenService, - OAuth2GrantType.JWT_BEARER, + GrantType.JWT_BEARER, ), ) return execution.execute(request, body) @@ -107,7 +108,7 @@ private fun genererAccessToken( request: HttpRequest, clientConfigurationProperties: ClientConfigurationProperties, oAuth2AccessTokenService: OAuth2AccessTokenService, - grantType: OAuth2GrantType? = null, + grantType: GrantType? = null, ): String { val clientProperties = clientPropertiesFor( request.uri, @@ -115,6 +116,7 @@ private fun genererAccessToken( grantType, ) return oAuth2AccessTokenService.getAccessToken(clientProperties).accessToken + ?: throw JwtTokenMissingException() } /** @@ -127,7 +129,7 @@ private fun genererAccessToken( private fun clientPropertiesFor( uri: URI, clientConfigurationProperties: ClientConfigurationProperties, - grantType: OAuth2GrantType?, + grantType: GrantType?, ): ClientProperties { val clientProperties = filterClientProperties(clientConfigurationProperties, uri) return if (grantType == null) { @@ -151,7 +153,7 @@ private fun filterClientProperties( private fun clientPropertiesForGrantType( values: List, - grantType: OAuth2GrantType, + grantType: GrantType, uri: URI, ): ClientProperties { return values.firstOrNull { grantType == it.grantType } @@ -159,12 +161,12 @@ private fun clientPropertiesForGrantType( } private fun clientCredentialOrJwtBearer() = - if (erSystembruker()) OAuth2GrantType.CLIENT_CREDENTIALS else OAuth2GrantType.JWT_BEARER + if (erSystembruker()) GrantType.CLIENT_CREDENTIALS else GrantType.JWT_BEARER private fun erSystembruker(): Boolean { return try { - val preferred_username = - SpringTokenValidationContextHolder().tokenValidationContext.getClaims("azuread")["preferred_username"] + val tokenValidationContext = SpringTokenValidationContextHolder().getTokenValidationContext() + val preferred_username = tokenValidationContext.getClaims("azuread").get("preferred_username") return preferred_username == null } catch (e: Throwable) { // Ingen request context. Skjer ved kall som har opphav i kjørende applikasjon. Ping etc. diff --git a/http-client/test/no/nav/tilleggsstonader/libs/http/client/RetryOAuth2HttpClientTest.kt b/http-client/test/no/nav/tilleggsstonader/libs/http/client/RetryOAuth2HttpClientTest.kt index ae6dd20..96e170c 100644 --- a/http-client/test/no/nav/tilleggsstonader/libs/http/client/RetryOAuth2HttpClientTest.kt +++ b/http-client/test/no/nav/tilleggsstonader/libs/http/client/RetryOAuth2HttpClientTest.kt @@ -12,18 +12,24 @@ import org.junit.jupiter.api.AfterAll import org.junit.jupiter.api.BeforeAll import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test -import org.springframework.boot.web.client.RestTemplateBuilder +import org.springframework.boot.web.client.ClientHttpRequestFactories +import org.springframework.boot.web.client.ClientHttpRequestFactorySettings +import org.springframework.web.client.RestClient import java.net.URI import java.time.Duration import java.time.temporal.ChronoUnit internal class RetryOAuth2HttpClientTest { - private val restTemplateBuilder = RestTemplateBuilder() - .setConnectTimeout(Duration.of(1, ChronoUnit.SECONDS)) - .setReadTimeout(Duration.of(1, ChronoUnit.SECONDS)) + val clientHttpRequestFactorySettings = ClientHttpRequestFactorySettings.DEFAULTS + .withConnectTimeout(Duration.of(1, ChronoUnit.SECONDS)) + .withReadTimeout(Duration.of(1, ChronoUnit.SECONDS)) - private val client = RetryOAuth2HttpClient(restTemplateBuilder) + val requestFactory = ClientHttpRequestFactories.get(clientHttpRequestFactorySettings) + val restClient = RestClient.builder() + .requestFactory(requestFactory) + .build() + val client = RetryOAuth2HttpClient(restClient) @BeforeEach internal fun setUp() { @@ -51,13 +57,6 @@ internal class RetryOAuth2HttpClientTest { wireMockServer.verify(2, RequestPatternBuilder.allRequests()) } - @Test - internal fun `502 - skal prøve på nytt`() { - stub(WireMock.serverError().withStatus(502)) - post() - wireMockServer.verify(3, RequestPatternBuilder.allRequests()) - } - @Test internal fun `socketException - skal prøve på nytt`() { stub(WireMock.serverError().withFault(Fault.CONNECTION_RESET_BY_PEER)) @@ -82,8 +81,7 @@ internal class RetryOAuth2HttpClientTest { private fun post(): Exception? { return try { client.post( - OAuth2HttpRequest.builder() - .tokenEndpointUrl(URI.create(wireMockServer.baseUrl())) + OAuth2HttpRequest.builder(URI.create(wireMockServer.baseUrl())) .oAuth2HttpHeaders(OAuth2HttpHeaders.builder().build()) .build(), ) diff --git a/http-client/test/no/nav/tilleggsstonader/libs/http/interceptor/BearerTokenClientCredentialsClientInterceptorTest.kt b/http-client/test/no/nav/tilleggsstonader/libs/http/interceptor/BearerTokenClientCredentialsClientInterceptorTest.kt index d2e0100..298c27d 100644 --- a/http-client/test/no/nav/tilleggsstonader/libs/http/interceptor/BearerTokenClientCredentialsClientInterceptorTest.kt +++ b/http-client/test/no/nav/tilleggsstonader/libs/http/interceptor/BearerTokenClientCredentialsClientInterceptorTest.kt @@ -34,7 +34,7 @@ internal class BearerTokenClientCredentialsClientInterceptorTest { bearerTokenClientInterceptor.intercept(req, ByteArray(0), execution) - verify { oAuth2AccessTokenService.getAccessToken(clientConfigurationProperties.registration["1"]) } + verify { oAuth2AccessTokenService.getAccessToken(clientConfigurationProperties.registration["1"]!!) } } @Test @@ -43,6 +43,6 @@ internal class BearerTokenClientCredentialsClientInterceptorTest { every { req.uri } returns (URI("http://jwtResource.no")) val execution = mockk(relaxed = true) assertThat(catchThrowable { bearerTokenClientInterceptor.intercept(req, ByteArray(0), execution) }) - .hasMessage("could not find oauth2 client config for uri=http://jwtResource.no and grant type=OAuth2GrantType[value=client_credentials]") + .hasMessage("could not find oauth2 client config for uri=http://jwtResource.no and grant type=client_credentials") } } diff --git a/http-client/test/no/nav/tilleggsstonader/libs/http/interceptor/BearerTokenClientInterceptorTest.kt b/http-client/test/no/nav/tilleggsstonader/libs/http/interceptor/BearerTokenClientInterceptorTest.kt index d86c15e..bdab969 100644 --- a/http-client/test/no/nav/tilleggsstonader/libs/http/interceptor/BearerTokenClientInterceptorTest.kt +++ b/http-client/test/no/nav/tilleggsstonader/libs/http/interceptor/BearerTokenClientInterceptorTest.kt @@ -43,7 +43,7 @@ class BearerTokenClientInterceptorTest { bearerTokenClientInterceptor.intercept(req, ByteArray(0), execution) - verify { oAuth2AccessTokenService.getAccessToken(clientConfigurationProperties.registration["1"]) } + verify { oAuth2AccessTokenService.getAccessToken(clientConfigurationProperties.registration["1"]!!) } } @Test @@ -56,7 +56,7 @@ class BearerTokenClientInterceptorTest { bearerTokenClientInterceptor.intercept(req, ByteArray(0), execution) - verify { oAuth2AccessTokenService.getAccessToken(clientConfigurationProperties.registration["2"]) } + verify { oAuth2AccessTokenService.getAccessToken(clientConfigurationProperties.registration["2"]!!) } } fun mockBrukerContext(preferredUsername: String) { diff --git a/http-client/test/no/nav/tilleggsstonader/libs/http/interceptor/BearerTokenOnBehalfOfClientInterceptorTest.kt b/http-client/test/no/nav/tilleggsstonader/libs/http/interceptor/BearerTokenOnBehalfOfClientInterceptorTest.kt index 0450dcb..84dd11b 100644 --- a/http-client/test/no/nav/tilleggsstonader/libs/http/interceptor/BearerTokenOnBehalfOfClientInterceptorTest.kt +++ b/http-client/test/no/nav/tilleggsstonader/libs/http/interceptor/BearerTokenOnBehalfOfClientInterceptorTest.kt @@ -33,7 +33,7 @@ internal class BearerTokenOnBehalfOfClientInterceptorTest { bearerTokenClientInterceptor.intercept(req, ByteArray(0), execution) - verify { oAuth2AccessTokenService.getAccessToken(clientConfigurationProperties.registration["2"]) } + verify { oAuth2AccessTokenService.getAccessToken(clientConfigurationProperties.registration["2"]!!) } } @Test @@ -50,6 +50,6 @@ internal class BearerTokenOnBehalfOfClientInterceptorTest { ) }, ) - .hasMessage("could not find oauth2 client config for uri=http://clientResource.no and grant type=OAuth2GrantType[value=urn:ietf:params:oauth:grant-type:jwt-bearer]") + .hasMessage("could not find oauth2 client config for uri=http://clientResource.no and grant type=urn:ietf:params:oauth:grant-type:jwt-bearer") } } diff --git a/http-client/test/no/nav/tilleggsstonader/libs/http/interceptor/ClientConfigTestProperties.kt b/http-client/test/no/nav/tilleggsstonader/libs/http/interceptor/ClientConfigTestProperties.kt index 8548fae..9c319ec 100644 --- a/http-client/test/no/nav/tilleggsstonader/libs/http/interceptor/ClientConfigTestProperties.kt +++ b/http-client/test/no/nav/tilleggsstonader/libs/http/interceptor/ClientConfigTestProperties.kt @@ -1,9 +1,9 @@ package no.nav.tilleggsstonader.libs.http.interceptor +import com.nimbusds.oauth2.sdk.GrantType import com.nimbusds.oauth2.sdk.auth.ClientAuthenticationMethod import no.nav.security.token.support.client.core.ClientAuthenticationProperties import no.nav.security.token.support.client.core.ClientProperties -import no.nav.security.token.support.client.core.OAuth2GrantType import no.nav.security.token.support.client.spring.ClientConfigurationProperties import java.net.URI @@ -20,7 +20,7 @@ val clientConfigurationProperties = "1" to ClientProperties( URI(tokenEndpoint), URI(tokenEndpoint), - OAuth2GrantType.CLIENT_CREDENTIALS, + GrantType.CLIENT_CREDENTIALS, listOf("z", "y", "x"), authentication, URI("http://firstResource.no"), @@ -29,7 +29,7 @@ val clientConfigurationProperties = "2" to ClientProperties( URI(tokenEndpoint), URI(tokenEndpoint), - OAuth2GrantType.JWT_BEARER, + GrantType.JWT_BEARER, listOf("c", "b", "a"), authentication, URI("http://firstResource.no"), @@ -38,7 +38,7 @@ val clientConfigurationProperties = "3" to ClientProperties( URI(tokenEndpoint), URI(tokenEndpoint), - OAuth2GrantType.JWT_BEARER, + GrantType.JWT_BEARER, listOf("z", "y", "x"), authentication, URI("http://jwtResource.no"), @@ -47,7 +47,7 @@ val clientConfigurationProperties = "4" to ClientProperties( URI(tokenEndpoint), URI(tokenEndpoint), - OAuth2GrantType.CLIENT_CREDENTIALS, + GrantType.CLIENT_CREDENTIALS, listOf("z", "y", "x"), authentication, URI("http://clientResource.no"), diff --git a/settings.gradle.kts b/settings.gradle.kts index f873224..e94fe3a 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,7 +1,7 @@ rootProject.name = "tilleggsstonader-libs" plugins { - id("org.gradle.toolchains.foojay-resolver-convention") version "0.7.0" + id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0" } include("http-client") diff --git a/sikkerhet/build.gradle.kts b/sikkerhet/build.gradle.kts index d63f81d..bf964ee 100644 --- a/sikkerhet/build.gradle.kts +++ b/sikkerhet/build.gradle.kts @@ -1,4 +1,4 @@ -val tokenSupportVersion = "3.2.0" +val tokenSupportVersion = "4.1.3" plugins { kotlin("plugin.spring") version "1.9.22" diff --git a/sikkerhet/main/no.nav.tilleggsstonader.libs.sikkerhet/EksternBrukerUtils.kt b/sikkerhet/main/no.nav.tilleggsstonader.libs.sikkerhet/EksternBrukerUtils.kt index 680bb44..957c799 100644 --- a/sikkerhet/main/no.nav.tilleggsstonader.libs.sikkerhet/EksternBrukerUtils.kt +++ b/sikkerhet/main/no.nav.tilleggsstonader.libs.sikkerhet/EksternBrukerUtils.kt @@ -1,6 +1,7 @@ package no.nav.tilleggsstonader.libs.sikkerhet import no.nav.security.token.support.core.context.TokenValidationContext +import no.nav.security.token.support.core.exceptions.JwtTokenMissingException import no.nav.security.token.support.core.exceptions.JwtTokenValidatorException import no.nav.security.token.support.core.jwt.JwtTokenClaims import no.nav.security.token.support.spring.SpringTokenValidationContextHolder @@ -29,7 +30,7 @@ object EksternBrukerUtils { fun getBearerTokenForLoggedInUser(): String { return getFromContext { validationContext, issuer -> - validationContext.getJwtToken(issuer).tokenAsString + validationContext.getJwtToken(issuer)?.encodedToken ?: throw JwtTokenMissingException() } }