-
Notifications
You must be signed in to change notification settings - Fork 6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support different OIDC issuer hostnames for frontend/backend endpoints #14633
Comments
Hi, @heruan! Thanks for the report. I think the sticky part is the following from the Authorization Server Metadata spec:
Because of that, Spring Security requires they match by default, which generates a question: Are you able to query using the external URI? If not, the Boot property might not be the best fit for your arrangement. I don't think we want to add something that switches off spec-related validation, but I think it does make sense to add @Bean
ClientRegistrationRepository clients(RestTemplate rest) {
Map<String, Object> metadata = rest.getForObject(...);
// ... validate on your own
return new InMemoryClientRegistrationRepository(ClientRegistrations.fromMetadata(metadata));
} Would either of these approaches work for you? |
Hey @jzheaux thanks for the feedback! I'm not able to query using the external URI, so I'd need another approach. How would the spring:
security:
oauth2:
client:
provider:
keycloak:
issuer-uri: http://internal:8180/realms/foo
registration:
keycloak:
client-id: my-client
client-secret: my-secret
scope:
- openid With the current implementation the I suppose that would be somewhere around here: OAuth2ClientPropertiesMapper.java#L71-L74 I need this to be configuration based since applications run in a cluster and configuration is provisioned with config-maps so if I add code it should be generic enough to work with different configurations. |
I've tried different approaches to this without much luck. Only configuring with Boot properties successfully configures everything needed for OIDC to work properly, e.g. scopes, tokens, back-channel logout, etc. That part of the spec you mentioned looks to be known to be problematic, as it doesn't take into account valid scenarios like the one I described where the app communicates with the issuer with a backend URL. Quarkus provides a way to skip issuer validation in that sense and also Vault is dealing with this. Would it be acceptable to have a Boot property to specify the issuer value expected from metadata to perform validation? spring:
security:
oauth2:
client:
provider:
keycloak:
issuer-uri: http://internal:8180/realms/foo
metadata-issuer: https://external/realms/foo |
@jzheaux any further comments on this? It's also being discussed in keycloak/keycloak#24252 (comment) first and then keycloak/keycloak#29783 for a Keycloak-specific approach, but I read mentions of the same topic being an issue in Vault and other OIDC based projects. |
I'm having the same issue, any solution or workaround for now? |
The easiest "workaround" is to not set the spring:
security:
oauth2:
resourceserver:
jwt:
jwk-set-uri: https://idp.example.org/.well-known/jwks.json Without the
Also the issuer validation with the JwtIssuerValidator will not be active. If the validation is needed, one could bring it back by configuring the JwtDecoder as a bean. |
I have the same problem (Spring boot + Keycloak behind nginx reverse proxy -> all in docker containers). The workaround that I use is to set alias on the nginx container (alias=external.com) and from spring --> keycloak.issuer-uri: https://external.com..... |
I have created PR #15716 to address this with the minimum changes required to support the mentioned scenarios. If it gets merged, additional support for e.g. Spring Boot configuration properties could be discussed. |
This workaround only applies for a resource server, while for OIDC also other endpoints are fetched from the issuer. |
Indeed, this only works for a resource server. The solution of @vbbonev is best when using plain docker containers. I am using Kubernetes, my currentworkaround is to add a rewrite rule to coredns, e.g.:
I tested this locally and using this on Azure (AKS) in production:
Obviously with your issuer-uri provided. This would not be needed anymore with the PR of @heruan. |
This on the other hand can only be done if you have control over the CoreDNS configuration. |
Thanks for all the research and the PR, @heruan. Since there is a fair amount of content here, I'd like to catch up before proceeding to the PR.
There might be some confusion there; the code compares the
I'm sorry that this hasn't worked yet for you. The approach I posted doesn't use the Boot properties as stated; however, you could depend on the Boot properties like so: @Bean
ClientRegistrationRepository clients(OAuth2ClientProperties clients, RestTemplate rest) {
Map<String, Object> metadata = rest.getForObject(...);
// ... validate on your own
ClientRegistration registration = ClientRegistrations.fromMetadata(metadata);
// ... set scopes, etc.
return new InMemoryClientRegistrationRepository(registration);
} Given the number of votes, I don't mind raising this with the team to see what can be done to simplify this. |
Thanks @jzheaux for the renewed interest in this! With your latest suggestion I was able to configure my client registration properly adding this method to public static ClientRegistration.Builder fromOidcConfiguration(Map<String, Object> configuration) {
OIDCProviderMetadata metadata = parse(configuration, OIDCProviderMetadata::parse);
ClientRegistration.Builder builder = withProviderConfiguration(metadata, metadata.getIssuer().getValue());
builder.jwkSetUri(metadata.getJWKSetURI().toASCIIString());
if (metadata.getUserInfoEndpointURI() != null) {
builder.userInfoUri(metadata.getUserInfoEndpointURI().toASCIIString());
}
return builder;
} I can update my PR to add this method instead of the current approach with the two different URIs. |
ClientRegistrations now provides the fromOidcConfiguration method to create a ClientRegistration.Builder from a map representation of an OpenID Provider Configuration Response. This is useful when the OpenID Provider Configuration is not available at a well-known location, or if custom validation is needed for the issuer location (e.g. if the issuer is only reachable via a back-channel URI that is different from the issuer value in the configuration). Fixes: spring-projectsgh-14633
ClientRegistrations now provides the fromOidcConfiguration method to create a ClientRegistration.Builder from a map representation of an OpenID Provider Configuration Response. This is useful when the OpenID Provider Configuration is not available at a well-known location, or if custom validation is needed for the issuer location (e.g. if the issuer is only reachable via a back-channel URI that is different from the issuer value in the configuration). Fixes: spring-projectsgh-14633
ClientRegistrations now provides the fromOidcConfiguration method to create a ClientRegistration.Builder from a map representation of an OpenID Provider Configuration Response. This is useful when the OpenID Provider Configuration is not available at a well-known location, or if custom validation is needed for the issuer location (e.g. if the issuer is only reachable via a back-channel URI that is different from the issuer value in the configuration). Fixes: spring-projectsgh-14633
@jzheaux I have updated the PR so that it now only adds the method you suggested. Hope it helps going forward with this! |
My problem is simpler, my app and keycloak are in the same vpc on aws. Keycloak has a public hostname starting with |
Sounds the same issue to me: the front-channel and the back-channel URIs differ, if just for the scheme. Unfortunately the spec does not consider such case either. You should be able to use #15716 and build a |
Thanks for creating this ticket I'm closing this in favor of gh-15716 |
ClientRegistrations now provides the fromOidcConfiguration method to create a ClientRegistration.Builder from a map representation of an OpenID Provider Configuration Response. This is useful when the OpenID Provider Configuration is not available at a well-known location, or if custom validation is needed for the issuer location (e.g. if the issuer is only reachable via a back-channel URI that is different from the issuer value in the configuration). Fixes: gh-14633
We use a service called localhost to be able to address the keycloak service as "localhost:8443" in the application.yaml of our application. Using "keycloak:8443" is not possible as spring would then redirect the user to "keycloak:8443" to login, but that is a host that is not available on the host system. The spring boot oauth implementation is very restrictive on the issuer and therefore creates errors if the hosts used by it and the user to connect to keycloak differ. See also: - spring-projects/spring-security#14633 - keycloak/keycloak#29783 - keycloak/keycloak#24252 - https://medium.com/@kostapchuk/integrating-keycloak-with-spring-boot-in-a-dockerized-environment-813eab1f140c RISDEV-5805
Expected Behavior
When the OIDC provider uses different hostnames from frontend and backend endpoints, fetching metadata from the configure issuer hostname does not fail.
Current Behavior
If the frontend and backend hostnames differs when fetching metadata, a validation exception is thrown.
Context
Consider a Spring application running inside a Kubernetes cluster, and it needs to authenticate against an OIDC server inside the same cluster (say Keycloak). Hostnames used inside the cluster are different from the public ones, and this is supported by Keycloak (frontend and backend hostnames can be different).
The Spring app has this configuration:
When fetching metadata from there, Keycloak returns:
As expected, the frontend endpoints use
https://external
and backend endpoints usehttp://internal:8180
.The problem is that when fetching metadata, Spring fails here:
spring-security/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/registration/ClientRegistrations.java
Lines 246 to 248 in 9c6b5f9
Fetching metadata from the issuer is necessary since some endpoints are read only from metadata, e.g the
end_session_endpoint
:spring-security/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/web/logout/OidcClientInitiatedLogoutSuccessHandler.java
Lines 79 to 80 in 9c6b5f9
But also other back-channel endpoints without configuration properties in
application.yaml
.Since I know both internal and external hostnames, how can I make Spring Security fetch metadata from the internal issuer hostname and accept the external hostname in metadata?
Note: a similar scenario has been solved for JWT decoders in #10309
The text was updated successfully, but these errors were encountered: