Describe the bug
When ClientRegistrations is used to obtain meta data from issuer that has an invalid HTTPS certificate (in my case, self-signed), it fails with error
Caused by: java.lang.IllegalArgumentException: Unable to resolve Configuration with the provided Issuer of "https://redacted/keycloak/realms/redacted"
at org.springframework.security.oauth2.client.registration.ClientRegistrations.getBuilder(ClientRegistrations.java:228)
at org.springframework.security.oauth2.client.registration.ClientRegistrations.fromIssuerLocation(ClientRegistrations.java:152)
at org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientPropertiesMapper.getBuilderFromIssuerIfPossible(OAuth2ClientPropertiesMapper.java:97)
Caused by: org.springframework.web.client.ResourceAccessException: I/O error on GET request for "https://redacted/keycloak/realms/redacted/.well-known/openid-configuration": PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path t
o requested target
at org.springframework.web.client.RestTemplate.createResourceAccessException(RestTemplate.java:905)
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:885)
at org.springframework.web.client.RestTemplate.exchange(RestTemplate.java:731)
at org.springframework.security.oauth2.client.registration.ClientRegistrations.lambda$oidc$0(ClientRegistrations.java:163)
at org.springframework.security.oauth2.client.registration.ClientRegistrations.getBuilder(ClientRegistrations.java:216)
To Reproduce
Minimal application.yml
spring:
security:
oauth2:
client:
registration:
keycloak:
client-id: secret
client-secret: secret
provider:
keycloak:
issuer-uri: https://redacted/keycloak/realms/realm
user-name-attribute: preferred_username
Dependencies
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-client</artifactId>
</dependency>
Configuration bean
@Configuration
@EnableWebFluxSecurity
public class SecurityConfiguration {
@Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
return http.oauth2Login(withDefaults()).build();
}
}
Spring Boot 3.2.0-RC2, Spring Security 6.2.0-RC2
Expected behavior
I can override WebClient instances for each service that calls my provider and provide a valid TLS certification path. But I cannot do it for ClientRegistrations.
Sample
A link to a GitHub repository with a minimal, reproducible sample.
Reports that include a sample will take priority over reports that do not. At times, we may require a sample, so it is good to try and include a sample up front.
Comment From: sjohnr
@ZIRAKrezovic, thanks for reaching out!
In 6.2 (recently released), there is a new feature available (see gh-11783) which allows you to more easily configure a RestTemplate
for OAuth2 Client components. See docs. This improvement only exists for servlet applications, with reactive support (gh-13763) planned for a future release.
However, regarding ClientRegistrations
please see this comment:
ClientRegistrations
is intended to be used as a utility/convenience class. It was designed to fulfill most use cases, however, it may not be suitable for certain use cases. For example, if the internal network traffic must be routed through a Proxy, you can bypass discovery by configuring theauthorization-uri
andtoken-uri
property instead of theissuer-uri
property.NOTE: The underlying HTTP Client used in
ClientRegistrations
was purposely encapsulated and there is no plan to expose it.
I'm going to close this issue with the above explanation.
Comment From: ZIRAKrezovic
Hi @sjohnr ... the problem here is without this "internal" class, there is no way to support RP initiated logout because the endsession_uri is configured only via the mentioned ClientRegistrations utility class.
Comment From: sjohnr
@ZIRAKrezovic, I don't think I follow. You can configure RP-initiated logout as per the documentation.
there is no way to support RP initiated logout because the endsession_uri is configured only via the mentioned ClientRegistrations utility class.
I don't think this is correct, as the OidcClientInitiatedLogoutSuccessHandler
simply accesses the end_session_endpoint
via the following:
clientRegistration.getProviderDetails().getConfigurationMetadata().get("end_session_endpoint")
which can be specified via ClientRegistration.Builder#providerConfigurationMetadata(Map)
.
Comment From: ZIRAKrezovic
@sjohnr Forgot to add further context. I am using spring-boot + auto configuration. There is no way to do it with spring autoconfigured client registrations + repository. Thanks for the info, I may have to override a bit more than I'd like to.
Comment From: ZIRAKrezovic
It's somewhat a hack, but it works ... in case somebody else needs it
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.boot.context.properties.bind.BindResult;
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.context.EnvironmentAware;
import org.springframework.core.ResolvableType;
import org.springframework.core.env.Environment;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.registration.InMemoryReactiveClientRegistrationRepository;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
@Slf4j
public class OidcMetadataBeanPostProcessor
implements BeanPostProcessor, InitializingBean, EnvironmentAware {
private Environment environment;
private Map<String, Map<String, String>> properties;
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
if (bean instanceof InMemoryReactiveClientRegistrationRepository repo) {
return replace(repo);
}
return bean;
}
private InMemoryReactiveClientRegistrationRepository replace(
InMemoryReactiveClientRegistrationRepository repo) {
List<ClientRegistration> registrations = new ArrayList<>();
List<ClientRegistration> modifiedRegistrations = new ArrayList<>();
repo.forEach(registrations::add);
Iterator<ClientRegistration> it = registrations.iterator();
while (it.hasNext()) {
var registration = it.next();
if (properties.containsKey(registration.getRegistrationId())) {
var details = registration.getProviderDetails();
Map<String, Object> metadata = new HashMap<>(details.getConfigurationMetadata());
metadata.putAll(properties.get(registration.getRegistrationId()));
modifiedRegistrations.add(
ClientRegistration.withClientRegistration(registration)
.providerConfigurationMetadata(metadata)
.build());
it.remove();
}
}
registrations.addAll(modifiedRegistrations);
if (modifiedRegistrations.isEmpty()) {
return repo;
} else {
log.debug(
"Modified [{}] client registrations with metadata from 'security.oidc-meta-data'",
modifiedRegistrations.size());
return new InMemoryReactiveClientRegistrationRepository(registrations);
}
}
@Override
public void afterPropertiesSet() {
BindResult<Map<String, Map<String, String>>> bind =
Binder.get(environment)
.bind(
"security.oidc-meta-data",
Bindable.of(
ResolvableType.forClassWithGenerics(
Map.class,
ResolvableType.forClass(String.class),
ResolvableType.forClassWithGenerics(
Map.class, String.class, String.class))));
if (bind.isBound()) {
properties = bind.get();
} else {
properties = Collections.emptyMap();
}
}
@Override
public void setEnvironment(Environment environment) {
this.environment = environment;
}
}
And application.yaml
security:
oidc-meta-data:
keycloak:
first: second
Comment From: sjohnr
@ZIRAKrezovic, I would also recommend you look into simply providing your ClientRegistration
programmatically.
You can of course fetch details from the https://redacted/keycloak/realms/myrealm/.well-known/openid-configuration
URL yourself if needed. Or you can simply read in those details from your own properties. Either way, you can specify your provider details like end_session_endpoint
as part of that without needing to post-process an auto-configuration bean.
Comment From: eswarakatyayani
Hi I am also getting the same issue, How to resolve it.
Comment From: eswarakatyayani
Exception occurred in generateToken I/O error on POST request for "https://xyzabc/oauth/client_credential/accesstoken": (certificate_unknown) PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target; nested exception is javax.net.ssl.SSLHandshakeException: (certificate_unknown) PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested targetjavax.net.ssl.SSLHandshakeException: (certificate_unknown) PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target