tutorials
tutorials copied to clipboard
[ClientRegistration Issue When Using Reverse-Proxy Configuration]
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:
- In the link above at line 8, the issuer is assigned this value: ${reverse-proxy-uri}${authorization-server-prefix}/realms/baeldung
- 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.
- If the server doesn't use the BFF pattern, this wouldn't be an issue, because this comparison will always be true.
- 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:
- 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 + "\"";
});
- 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