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.
- 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
- 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}
- 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()
}
}