I am not really developer, so someone needs to check this but I have the impression these are buggy. Important with testing is thus that you block every other outgoing traffic except to the proxy. Otherwise you do not notice that some request are going past the proxy.

  1. when running in a proxy environment only, the issuer-uri is being gotten without proxy, resulting in a startup failure

java -Dhttps.proxyHost= -Dhttps.proxyPort= etc

  1. when commenting out the issuer-uri, something changes in the auth process and even though all custom endpoints are taken directly from the issuer-uri. Some issue arises during Spring Security's processing of the token, likely in .oauth2Login, when it tries to validate the token's claims or associate it with the client registration.
     27     oauth2:
     28       resourceserver:
     29         jwt:
     30           issuer-uri: ${JWT_URI}
     31           jwt-custom-uri: ${JWT_URI}
     32           jwk-set-uri: ${OIDC_JWKCRTURI}
     33           connect-timeout: 5000
     34           read-timeout: 5000
     35       client:
     36         registration:
     37           keycloak:
     38             client-id: ${OIDC_CLIENTID}
     39             client-secret: ${OIDC_CLIENTSECRET}
     40             authorization-grant-type: authorization_code
     41             scope: ${OIDC_SCOPE:openid}
     42             provider: keycloak
     43             redirect-uri: "{baseUrl}/login/oauth2/code/{registrationId}"
     44         provider:
     45           keycloak:
     46             #issuer-uri: ${OIDC_ISSUERURI} #breaks startup
     47             authorization-uri: ${OIDC_AUTHURI}
     48             token-uri: ${OIDC_TOKENURI}
     49             user-info-uri: ${OIDC_USERURI}
     50             jwk-set-uri: ${OIDC_JWKCRTURI}
  1. I think it is also weird that an application fails on startup when some external service is not available. The default should be just starting no need to check and download stuff.

https://stackoverflow.com/questions/79551156/spring-boot-starter-oauth2-client-app-never-starts-when-oauth-host-unreachable

https://github.com/spring-projects/spring-boot/issues/44985#event-17107137936

Comment From: DW8Reaper

I was on my way to log a similar issue, our application uses webflux so the underlying default implementation is Netty and Reactor. In short the default HttpClient for netty does not use the standard java proxy variables so it is pointless to just set the proxy. Although there is a work around we spent quite a lot of time looking for problems everywhere else in our environment thinking we had missed some configuration, we also have a lot of applications deployed that would all need to override their oauth settings.

My assumption about DefaultWebClientBuilder never using system proxies is possibly wrong maybe there is some mechanism to override its defaults that I just don't know about.

The problem is that AbstractWebClientReactiveOAuth2AccessTokenResponseClient calls WebClient.builder().build() which in turn uses DefaultWebClientBuilder and that will create the default Netty client without proxy support.

Once you are passed that and can read the issuer the next step is JWT validation which in our case loads a JWK set with the public keys. This is validated by ReactiveOidcIdTokenDecoderFactory it has this code to create a Nimbus validator:

return NimbusReactiveJwtDecoder.withJwkSetUri(jwkSetUri)
    .jwsAlgorithm((SignatureAlgorithm) jwsAlgorithm)
    .build();

and NimbusReactiveJwtDecoder is hard coded with a WebClient made with WebClient.create() which also uses DefaultWebClientBuilder and will not setup a proxy.

It would be great if AbstractWebClientReactiveOAuth2AccessTokenResponseClient and NimbusReactiveJwtDecoder would inject a WebClient.Builder instance and use that to create a client.

As a work around I created a WebClientCustomizer (we use kotlin so the syntax is a little different):

@Component
class ProxiedWebClientCustomizer : WebClientCustomizer {
    override fun customize(webClientBuilder: WebClient.Builder) {
        val customClient = HttpClient.create().proxyWithSystemProperties()

        webClientBuilder.clientConnector(ReactorClientHttpConnector(customClient))
    }
}

and then in my Spring Security setup I had to override the ReactiveAuthenticationManager in the oauth2login section (this):

oauth2Login {
    authenticationManager = WebClientReactiveAuthorizationCodeTokenResponseClient().let { oidcClient ->
        oidcClient.setWebClient(webClientBuilder.build())
        OidcAuthorizationCodeReactiveAuthenticationManager(oidcClient, OidcReactiveOAuth2UserService()).apply {
            setJwtDecoderFactory(proxiedJwtDecoderFactory)
        }
    }

the proxiedJwtDecoderFactory is a very basic factory that always just creates the Nimbus decoder since that is the only use case we have and it is way less flexible than the spring ReactiveOidcIdTokenDecoderFactory. Here is a simplified version of what we created:

@Component
class ProxiedJwtDecoderFactory(private val webClientBuilder: WebClient.Builder) : ReactiveJwtDecoderFactory<ClientRegistration> {

    override fun createDecoder(context: ClientRegistration?): ReactiveJwtDecoder {
        val jwkUri = context?.providerDetails?.jwkSetUri ?: throw IllegalArgumentException("No context or jwkSetUri")

        NimbusReactiveJwtDecoder
            .withJwkSetUri(jwkUri)
            .webClient(webClientBuilder.build())
            .build()
    }
}