I have a working Spring Cloud Gateway OAuth2 Client which is forwarding requests to OAuth2 Resource Servers behind Eureka and I would like to take the scalability a step further by enabling it to discover the Authorization Server as well through Eureka.
Basically, the problem is that in my current configuration is that my application.yml contains the following piece of code:
security:
oauth2:
client:
provider:
authorization-server:
issuer-uri: ${ISSUER_URI} # http://localhost:9000/auth
authorization-uri: ${AUTHORIZATION_URI} # http://localhost:9000/auth/oauth2/authorize
With such a configuration, if the Authorization Server goes under heavy load and scales out, it wouldn't be possible to select a different instance to forward requests by the gateway.
What I tried next is to move the configuration into Java code in order to use a DiscoveryClient
. What I came up with is the following:
@Configuration
@EnableWebFluxSecurity
public class SecurityConfiguration {
@Autowired
private DiscoveryClient discoveryClient;
// ...
private String getAuthorizationServerAddress() {
return this.discoveryClient.getInstances("authorization-server")
.get(0)
.getUri()
.toString();
}
@Bean
public ReactiveClientRegistrationRepository reactiveClientRegistrationRepository() {
var authorizationServerAddress = this.getAuthorizationServerAddress();
var clientRegistration = ClientRegistrations.fromOidcIssuerLocation(authorizationServerAddress + "/auth")
.registrationId(this.applicationName)
.authorizationUri(this.applicationApiUrl + "/auth/oauth2/authorize")
.clientId(this.applicationName)
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
.clientSecret(this.clientSecret)
.redirectUri(this.applicationBaseUrl + "/oauth2/login/code/" + this.applicationName)
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.scope(Set.of("openid", "profile", "offline_access"))
.build();
return new InMemoryReactiveClientRegistrationRepository(clientRegistration);
}
@Bean
public ReactiveOAuth2AuthorizedClientService authorizedClientService() {
return new InMemoryReactiveOAuth2AuthorizedClientService(this.reactiveClientRegistrationRepository());
}
@Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http,
ReactiveClientRegistrationRepository clientRegistrationRepository,
ReactiveOAuth2AuthorizedClientService oAuth2AuthorizedClientService,
ServerOAuth2AuthorizationRequestResolver resolver) {
// ...
http.oauth2Login(auth -> auth
.authenticationMatcher(new PathPatternParserServerWebExchangeMatcher("/oauth2/login/code/{registrationId}"))
.authenticationSuccessHandler(this.customLoginSuccessHandler)
.authorizationRequestResolver(resolver)
.clientRegistrationRepository(clientRegistrationRepository)
.authorizedClientService(oAuth2AuthorizedClientService)
);
// ...
return http.build();
}
Now this setup allows me to get the Authorization Server IP from Eureka but it only does at boot, meaning that every request the gateway will serve while it's on will be directed to that particular instance... which can easily be terminated over time if scaling-in happens.
I read that the solution could be to provide a custom WebClient bean with @LoadBalanced
annotation, but after my tests it looks like the gateway doesn't use it in the OAuth2 flow.
I tried to find more hints online but looks like I'm either providing the wrong search query or that the problem is still open.
The most generic solution would be to set something like this below
security:
oauth2:
client:
provider:
authorization-server:
issuer-uri: http://authorization-server/auth
authorization-uri: http://authorization-server/auth/oauth2/authorize
or at least intercept the requests before they are dispatched in order to modify the requested URI through DiscoveryClient.
Can anyone point me to the right direction? Thanks!