I have a spring cloud gateway as an oauth2 client that is serving a react front end. I am testing how it handles an expired refresh token. With no configuration it would throw a 500 internal server exception. More information can be found here https://github.com/spring-projects/spring-security/issues/11015
To fix this I tried the following:
@Log4j2
public class ReactiveOAuth2AuthorizedClientManagerCustom implements ReactiveOAuth2AuthorizedClientManager {
private final ReactiveClientRegistrationRepository clientRegistrationRepository;
private final ServerOAuth2AuthorizedClientRepository authorizedClientRepository;
private final ReactiveOAuth2AuthorizedClientManager authorizedClientManager;
public ReactiveOAuth2AuthorizedClientManagerCustom(ReactiveClientRegistrationRepository clientRegistrationRepository,
ServerOAuth2AuthorizedClientRepository authorizedClientRepository) {
this.clientRegistrationRepository = clientRegistrationRepository;
this.authorizedClientRepository = authorizedClientRepository;
this.authorizedClientManager = new DefaultReactiveOAuth2AuthorizedClientManager(
this.clientRegistrationRepository, this.authorizedClientRepository
);
}
public Mono<OAuth2AuthorizedClient> authorize(OAuth2AuthorizeRequest authorizeRequest) {
Assert.notNull(authorizeRequest.getClientRegistrationId(), "Client registration id cannot be null");
return this.authorizedClientManager.authorize(authorizeRequest)
.onErrorMap(
ClientAuthorizationException.class,
error -> {
return new ClientAuthorizationRequiredException(authorizeRequest.getClientRegistrationId());
});
}
}
@Log4j2
@Configuration
@EnableWebFluxSecurity
@RequiredArgsConstructor
public class SecurityConfig {
private final ReactiveClientRegistrationRepository clientRegistrationRepository;
private final ServerOAuth2AuthorizedClientRepository authorizedClientRepository;
@Bean
@Order(0)
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http
.oauth2Login(Customizer.withDefaults());
http.authorizeExchange(exchange -> exchange.anyExchange().authenticated());
return http.build();
}
@Bean
@Primary
ReactiveOAuth2AuthorizedClientManager authorizedClientManager() {
return new ReactiveOAuth2AuthorizedClientManagerCustom(
this.clientRegistrationRepository, this.authorizedClientRepository
);
}
Along with a web filter to handle that exception and redirect to the login page which is server by a spring oauth2 provider
@Component
@Log4j2
public class AuthorizationRedirectWebFilter implements WebFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
return chain.filter(exchange)
.onErrorResume(ClientAuthorizationRequiredException.class, ex -> redirectToLoginPage(exchange));
}
private Mono<Void> redirectToLoginPage(ServerWebExchange exchange) {
// Set the status code and the location header
exchange.getResponse().setStatusCode(HttpStatus.SEE_OTHER);
exchange.getResponse().getHeaders().setLocation(URI.create("/oauth2/authorization/gateway"));
return exchange.getResponse().setComplete();
}
}
When the refresh token expires I am redirected to my login page in the auth server as expected. But when I enter my credentials. I am redirected back to my spring cloud gateway /login?error page.
And when I click the login button I am automatically logged in without ever having to enter my credentials. Indicating that my first login was successful and my credentials were not invalid.
The thing I am confused the most about is that on my browser if I navigate directly to /oauth2/authorization/gateway everything works exactly as expected, as I am redirected to my auth server to login, I log in, and then I am redirected back to my react app that the spring cloud gateway is server. So why is it that when I try and do this redirection programically everything falls apart?
For even more context here is my spring cloud gateway config
spring:
cloud:
gateway:
routes:
- id: resource-server-route
uri: http://localhost:8080 # Base URL of your resource server
predicates:
- Path=/users, /user/**
filters:
TokenRelay=
security:
oauth2:
client:
registration:
gateway:
provider: auth-server
client-id: gateway
client-secret: secret
authorization-grant-type: authorization_code
redirect-uri: http://localhost:8090/login/oauth2/code/{registrationId}
scope: openid
provider:
auth-server:
issuer-uri: http://localhost:9000
