Describe the bug We implemented a Spring Cloud Gateway which is secured using an OAuth2 Login. It also relays the OAuth2 Token of the logged in user to downstream services, i.e. it acts as an OAuth2 client as well.
We wrote an integration test to verify that our setup is configured correctly. We followed the instructions in the Spring Security documentation and used mockOAuth2Login() for our integration test:
mockOAuth2Login()
.clientRegistration(repo.findByRegistrationId("keycloak").block()
)
We explicitly set a client registration that matches with our OAuth2 client configuration in our application.yaml file:
spring:
security:
oauth2:
client:
provider:
keycloak:
authorization-uri: ${gateway.target.uri.auth}/auth/realms/habitcentric/protocol/openid-connect/auth
token-uri: ${gateway.target.uri.auth}/auth/realms/habitcentric/protocol/openid-connect/token
user-info-uri: ${gateway.target.uri.auth}/auth/realms/habitcentric/protocol/openid-connect/userinfo
user-name-attribute: sub
jwk-set-uri: ${gateway.target.uri.auth}/auth/realms/habitcentric/protocol/openid-connect/certs
registration:
keycloak:
client-id: ${OAUTH_CLIENT_ID:gateway}
client-secret: ${OAUTH_CLIENT_SECRET:357638792F423F4528472B4B6250655368566D597133743677397A2443264629}
authorization-grant-type: authorization_code
redirect-uri: "{baseUrl}/login/oauth2/code/{registrationId}"
scope: openid
The docs of the clientConfigratuion method of mockOAuth2Login() states:
The supplied ClientRegistration will be registered into an WebSessionServerOAuth2AuthorizedClientRepository
Due to this, we expect that as soon as the user is logged in on the same OAuth2 authorization server as configured in the OAuth2 provider settings, Spring Security automatically registers an OAuth2AuthorizedClient so that the OAuth2 client is considered "authorized" in regards to the logged in user (who is also the resource owner).
To automatically register OAuth2AuthorizedClient instances, mockOAuth2Login() utilizes a TestReactiveOAuth2AuthorizedClientManager. However, the test implementation is not registered as a bean in the Spring context which causes the TokenRelayGatewayFilter of Spring Cloud Gateway to always use the DefaultReactiveOAuth2AuthorizedClientManager instead. This means that the authorization fails, and the TokenRelayGatewayFilter tries to re-authenticate by redirecting to the OAuth2 authorization server again.
The second issue is that the TestReactiveOAuth2AuthorizedClientManager doesn't read the ServerWebExchange from the Reactor Context, it only reads it from the OAuth2AuthorizeRequest. This must be fixed as well (see minimal reproducible sample below).
To Reproduce
1. Setup a Spring Cloud Gateway
2. Setup Spring Security configuration with OAuth2 login
3. Add Gateway route with a TokenRelay filter
4. Write an integration test utilizing the mockOAuth2Login() utility. Keep in mind to set the client registration name to the client registration configured in step 2. Assert that the result is 200 OK (happy path test).
Expected behavior Test is successful.
Actual behavior Test fails with error:
Status expected:<200 OK> but was:<302 FOUND>
Expected :200 OK
Actual :302 FOUND
Sample
Minimal reproducible sample: https://github.com/denniseffing/spring-security-mockoauth2login-issue
Take a look at the following file for workarounds for this issue: https://github.com/denniseffing/spring-security-mockoauth2login-issue/blob/main/src/test/kotlin/com/example/demo/SecurityConfigurationIntTestWithWorkaround.kt
Comment From: jzheaux
Good point, @denniseffing, it looks like this is an oversight. The original thought was to place it in the session by default; however, this was changed before the code went GA, and the JavaDoc was not updated at that time.
At the very least, the JavaDoc needs updating so it aligns with what is actually happening.
Additionally, though, I think that #8603 was not completed correctly. It should have changed from hard-coding a ServerOAuth2AuthorizedClientRepository to instrumenting the existing one. In this way, the client can be saved directly to the repository, and any found instance of a ReactiveOAuth2AuthorizedClientManager can be wrapped or configured to use the repository for that mock request. I'll see about repairing that in this ticket.