Skip to content
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

[ClientRegistration Issue When Using Reverse-Proxy Configuration] #16842

Open
amelongo opened this issue Jun 12, 2024 · 0 comments
Open

[ClientRegistration Issue When Using Reverse-Proxy Configuration] #16842

amelongo opened this issue Jun 12, 2024 · 0 comments
Labels
on-jira triaged Issues reviewed by a dev and considered valid. Will be added in Jira.

Comments

@amelongo
Copy link

amelongo commented Jun 12, 2024

Article and Module Links
https://github.com/eugenp/tutorials/blob/master/spring-security-modules/spring-security-oauth2-bff/backend/bff/src/main/resources/application.yml

Describe the Issue
When I try to use the BFF pattern with an OIDC authentication server, in my case Keycloak, I encounter a bug:

  1. In the link above at line 8, the issuer is assigned this value: ${reverse-proxy-uri}${authorization-server-prefix}/realms/baeldung
  2. However, per the ClientRegistration class defined in org.springframework.security.oauth2.client.registration, at line 132, there's a comparison between, the issuer provider above and the actual issuer info that comes from the server.
  3. If the server doesn't use the BFF pattern, this wouldn't be an issue, because this comparison will always be true.
  4. However if the authentication server is between a Reverse-Proxy, aka gateway, then this comparison will be false because the BFF pattern modifies the issuer uri as per 1. and although that modified link still point to the right server, the code at line 132 above would make it fails because there's not a one-to-one match between the modified uri in the bff configuration and the one returned from the server

To Reproduce
Steps to reproduce the behavior:

  1. BFF Configuration in Springboot:

#Keycloak configuration
scheme=http
hostname=localhost
reverse-proxy-port=7080
reverse-proxy-uri=${scheme}://${hostname}:${reverse-proxy-port}
authorization-server-prefix=/auth
issuer=${reverse-proxy-uri}${authorization-server-prefix}/realms/myrealm
client-id=myrealm-bff
client-secret=TrUt37XU00u2n0n30oW4isLiju2uG0wG
username-claim-json-path=$.preferred_username
authorities-json-path=$.realm_access.roles
bff-port=7081
bff-prefix=/bff
bff-uri=${scheme}://${hostname}:${bff-port}
resource-server-port=8083
audience=

#Gateway Paths configurations
spring.cloud.gateway.routes[0].id=bff
spring.cloud.gateway.routes[0].uri=${scheme}://${hostname}:${resource-server-port}
spring.cloud.gateway.routes[0].predicates[0]=Path=/api/**
spring.cloud.gateway.routes[0].filters[0]=DedupeResponseHeader=Access-Control-Allow-Credentials Access-Control-Allow-Origin
spring.cloud.gateway.routes[0].filters[1]=TokenRelay=
spring.cloud.gateway.routes[0].filters[2]=SaveSession
spring.cloud.gateway.routes[0].filters[3]=StripPrefix=1


#keycloak
spring.security.oauth2.client.provider.myrealm.issuer-uri=${issuer}
spring.security.oauth2.client.registration.myrealm.provider=myrealm
spring.security.oauth2.client.registration.myrealm.authorization-grant-type=authorization_code
spring.security.oauth2.client.registration.myrealm.client-id=${client-id}
spring.security.oauth2.client.registration.myrealm.client-secret=${client-secret}
spring.security.oauth2.client.registration.myrealm.scope=openid,profile,email,offline_access

#springaddons
com.c4-soft.springaddons.oidc.ops[0].iss=${issuer}
com.c4-soft.springaddons.oidc.ops[0].authorities[0].path=${authorities-json-path}
com.c4-soft.springaddons.oidc.ops[0].aud=${audience}

#securityFilterChain with oauth2Login() (sessions and CSRF protection enabled)
com.c4-soft.springaddons.oidc.client.client-uri=${reverse-proxy-uri}${bff-prefix}
com.c4-soft.springaddons.oidc.client.security-matchers[0]=/api/**
com.c4-soft.springaddons.oidc.client.security-matchers[1]=/login/**
com.c4-soft.springaddons.oidc.client.security-matchers[2]=/oauth2/**
com.c4-soft.springaddons.oidc.client.security-matchers[3]=/logout
com.c4-soft.springaddons.oidc.client.permit-all[0]=/api/**
com.c4-soft.springaddons.oidc.client.permit-all[1]=/login/**
com.c4-soft.springaddons.oidc.client.permit-all[2]=/oauth2/**
com.c4-soft.springaddons.oidc.client.post-logout-redirect-host=${hostname}
com.c4-soft.springaddons.oidc.client.csrf=cookie-accessible-from-js
com.c4-soft.springaddons.oidc.client.oauth2-redirections.rp-initiated-logout=ACCEPTED
com.c4-soft.springaddons.oidc.client.back-channel-logout.enabled=true

#securityFilterChain with oauth2ResourceServer() (sessions and CSRF protection disabled)
com.c4-soft.springaddons.oidc.resourceserver.permit-all[0]=/login-options
com.c4-soft.springaddons.oidc.resourceserver.permit-all[1]=/error
com.c4-soft.springaddons.oidc.resourceserver.permit-all[2]=/v3/api-docs/**
com.c4-soft.springaddons.oidc.resourceserver.permit-all[3]=/swagger-ui/**
com.c4-soft.springaddons.oidc.resourceserver.permit-all[4]=/actuator/health/readiness
com.c4-soft.springaddons.oidc.resourceserver.permit-all[5]=/actuator/health/liveness
5. Server configuration link: http://localhost:8080/realms/myrealm/.well-known/openid-configuration
    `{"issuer":"http://localhost:8080/realms/myrealm","authorization_endpoint":"http://localhost:8080/realms/myrealm/protocol/openid-connect/auth","token_endpoint":"http://localhost:8080/realms/myrealm/protocol/openid-connect/token","introspection_endpoint":"http://localhost:8080/realms/myrealm/protocol/openid-connect/token/introspect","userinfo_endpoint":"http://localhost:8080/realms/myrealm/protocol/openid-connect/userinfo","end_session_endpoint":"http://localhost:8080/realms/myrealm/protocol/openid-connect/logout","frontchannel_logout_session_supported":true,"frontchannel_logout_supported":true,"jwks_uri":"http://localhost:8080/realms/myrealm/protocol/openid-connect/certs","check_session_iframe":"http://localhost:8080/realms/myrealm/protocol/openid-connect/login-status-iframe.html","grant_types_supported":["authorization_code","implicit","refresh_token","password","client_credentials","urn:ietf:params:oauth:grant-type:device_code","urn:openid:params:grant-type:ciba"],"acr_values_supported":["0","1"],"response_types_supported":["code","none","id_token","token","id_token token","code id_token","code token","code id_token token"],"subject_types_supported":["public","pairwise"],"id_token_signing_alg_values_supported":["PS384","ES384","RS384","HS256","HS512","ES256","RS256","HS384","ES512","PS256","PS512","RS512"],"id_token_encryption_alg_values_supported":["RSA-OAEP","RSA-OAEP-256","RSA1_5"],"id_token_encryption_enc_values_supported":["A256GCM","A192GCM","A128GCM","A128CBC-HS256","A192CBC-HS384","A256CBC-HS512"],"userinfo_signing_alg_values_supported":["PS384","ES384","RS384","HS256","HS512","ES256","RS256","HS384","ES512","PS256","PS512","RS512","none"],"userinfo_encryption_alg_values_supported":["RSA-OAEP","RSA-OAEP-256","RSA1_5"],"userinfo_encryption_enc_values_supported":["A256GCM","A192GCM","A128GCM","A128CBC-HS256","A192CBC-HS384","A256CBC-HS512"],"request_object_signing_alg_values_supported":["PS384","ES384","RS384","HS256","HS512","ES256","RS256","HS384","ES512","PS256","PS512","RS512","none"],"request_object_encryption_alg_values_supported":["RSA-OAEP","RSA-OAEP-256","RSA1_5"],"request_object_encryption_enc_values_supported":["A256GCM","A192GCM","A128GCM","A128CBC-HS256","A192CBC-HS384","A256CBC-HS512"],"response_modes_supported":["query","fragment","form_post","query.jwt","fragment.jwt","form_post.jwt","jwt"],"registration_endpoint":"http://localhost:8080/realms/myrealm/clients-registrations/openid-connect","token_endpoint_auth_methods_supported":["private_key_jwt","client_secret_basic","client_secret_post","tls_client_auth","client_secret_jwt"],"token_endpoint_auth_signing_alg_values_supported":["PS384","ES384","RS384","HS256","HS512","ES256","RS256","HS384","ES512","PS256","PS512","RS512"],"introspection_endpoint_auth_methods_supported":["private_key_jwt","client_secret_basic","client_secret_post","tls_client_auth","client_secret_jwt"],"introspection_endpoint_auth_signing_alg_values_supported":["PS384","ES384","RS384","HS256","HS512","ES256","RS256","HS384","ES512","PS256","PS512","RS512"],"authorization_signing_alg_values_supported":["PS384","ES384","RS384","HS256","HS512","ES256","RS256","HS384","ES512","PS256","PS512","RS512"],"authorization_encryption_alg_values_supported":["RSA-OAEP","RSA-OAEP-256","RSA1_5"],"authorization_encryption_enc_values_supported":["A256GCM","A192GCM","A128GCM","A128CBC-HS256","A192CBC-HS384","A256CBC-HS512"],"claims_supported":["aud","sub","iss","auth_time","name","given_name","family_name","preferred_username","email","acr"],"claim_types_supported":["normal"],"claims_parameter_supported":true,"scopes_supported":["openid","profile","roles","web-origins","phone","microprofile-jwt","acr","email","address","offline_access"],"request_parameter_supported":true,"request_uri_parameter_supported":true,"require_request_uri_registration":true,"code_challenge_methods_supported":["plain","S256"],"tls_client_certificate_bound_access_tokens":true,"revocation_endpoint":"http://localhost:8080/realms/myrealm/protocol/openid-connect/revoke","revocation_endpoint_auth_methods_supported":["private_key_jwt","client_secret_basic","client_secret_post","tls_client_auth","client_secret_jwt"],"revocation_endpoint_auth_signing_alg_values_supported":["PS384","ES384","RS384","HS256","HS512","ES256","RS256","HS384","ES512","PS256","PS512","RS512"],"backchannel_logout_supported":true,"backchannel_logout_session_supported":true,"device_authorization_endpoint":"http://localhost:8080/realms/myrealm/protocol/openid-connect/auth/device","backchannel_token_delivery_modes_supported":["poll","ping"],"backchannel_authentication_endpoint":"http://localhost:8080/realms/myrealm/protocol/openid-connect/ext/ciba/auth","backchannel_authentication_request_signing_alg_values_supported":["PS384","ES384","RS384","ES256","RS256","ES512","PS256","PS512","RS512"],"require_pushed_authorization_requests":false,"pushed_authorization_request_endpoint":"http://localhost:8080/realms/myrealm/protocol/openid-connect/ext/par/request","mtls_endpoint_aliases":{"token_endpoint":"http://localhost:8080/realms/myrealm/protocol/openid-connect/token","revocation_endpoint":"http://localhost:8080/realms/myrealm/protocol/openid-connect/revoke","introspection_endpoint":"http://localhost:8080/realms/myrealm/protocol/openid-connect/token/introspect","device_authorization_endpoint":"http://localhost:8080/realms/myrealm/protocol/openid-connect/auth/device","registration_endpoint":"http://localhost:8080/realms/myrealm/clients-registrations/openid-connect","userinfo_endpoint":"http://localhost:8080/realms/myrealm/protocol/openid-connect/userinfo","pushed_authorization_request_endpoint":"http://localhost:8080/realms/myrealm/protocol/openid-connect/ext/par/request","backchannel_authentication_endpoint":"http://localhost:8080/realms/myrealm/protocol/openid-connect/ext/ciba/auth"}}`
7. The issuer value from the BFF configuration is: http://localhost:7080/auth/realms/myrealm
    The issuer value as retrieved in line 130 of ClientRegistrations is: http://localhost:8080/realms/myrealm
    These two lines are compared at line 131 of ClientRegistrations, 
            ```
Assert.state(issuer.equals(metadataIssuer), () -> {
            return "The Issuer \"" + metadataIssuer + "\" provided in the configuration metadata did not match the requested issuer \"" + issuer + "\"";
        });
  1. Because the two uris aren't equals, an exception is thrown:

Caused by: java.lang.IllegalStateException: The Issuer "http://localhost:8080/realms/myrealm" provided in the configuration metadata did not match the requested issuer "http://localhost:7080/auth/realms/myrealm"
at org.springframework.util.Assert.state(Assert.java:97) ~[spring-core-6.1.8.jar:6.1.8]
at org.springframework.security.oauth2.client.registration.ClientRegistrations.withProviderConfiguration(ClientRegistrations.java:246) ~[spring-security-oauth2-client-6.3.0.jar:6.3.0]
at org.springframework.security.oauth2.client.registration.ClientRegistrations.lambda$oidc$0(ClientRegistrations.java:165) ~[spring-security-oauth2-client-6.3.0.jar:6.3.0]
at org.springframework.security.oauth2.client.registration.ClientRegistrations.getBuilder(ClientRegistrations.java:216) ~[spring-security-oauth2-client-6.3.0.jar:6.3.0]
at org.springframework.security.oauth2.client.registration.ClientRegistrations.fromIssuerLocation(ClientRegistrations.java:152) ~[spring-security-oauth2-client-6.3.0.jar:6.3.0]
at org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientPropertiesMapper.getBuilderFromIssuerIfPossible(OAuth2ClientPropertiesMapper.java:97) ~[spring-boot-autoconfigure-3.3.0.jar:3.3.0]
at org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientPropertiesMapper.getClientRegistration(OAuth2ClientPropertiesMapper.java:71) ~[spring-boot-autoconfigure-3.3.0.jar:3.3.0]
at org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientPropertiesMapper.lambda$asClientRegistrations$0(OAuth2ClientPropertiesMapper.java:65) ~[spring-boot-autoconfigure-3.3.0.jar:3.3.0]
at java.base/java.util.HashMap.forEach(HashMap.java:1421) ~[na:na]
at org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientPropertiesMapper.asClientRegistrations(OAuth2ClientPropertiesMapper.java:64) ~[spring-boot-autoconfigure-3.3.0.jar:3.3.0]
at org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientRegistrationRepositoryConfiguration.clientRegistrationRepository(OAuth2ClientRegistrationRepositoryConfiguration.java:49) ~[spring-boot-autoconfigure-3.3.0.jar:3.3.0]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[na:na]
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
at java.base/java.lang.reflect.Method.invoke(Method.java:568) ~[na:na]
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:140) ~[spring-beans-6.1.8.jar:6.1.8]


**Expected Behavior**
Given that the BFF pattern only masks the link of the authentication server, the two links return the same result, so an exception shouldn't be thrown

**Screenshots**
If applicable, add screenshots to help explain your problem.

**Environment (please complete the following information):**
- OS: [e.g. Windows]
- Browser [e.g. chrome, safari]
- Version [e.g. 22]

**Additional Context**
The solution here would be to change the issuer in the BFF configuration to the actual value of the issuer but this will defeat the purpose of using a Reverse-Proxy and thus is not a recommended solution. If would be best, instead of only comparing the links values, the results return by these links should be compared as well. That way if a Reverse-Proxy is used, the first option, comparing the links, will fail but the second option, comparing the results return by the links, will succeed and thus the two links should be marked equal
@apeterlic apeterlic added the triaged Issues reviewed by a dev and considered valid. Will be added in Jira. label Jun 19, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
on-jira triaged Issues reviewed by a dev and considered valid. Will be added in Jira.
Projects
None yet
Development

No branches or pull requests

3 participants