spring-securityspring-cloud-gateway

Expose public endpoint through secured Spring Cloud Gateway


I am implementing spring security with OAuth2 in a microservice architecture which has a spring cloud gateway. Spring cloud gateway will be using TokenRelay filter to pass the JWT token to microservices. With the below implementation I am able to connect to any of the secured APIs in microservice. But I am unable to add an API which will be public (have permitAll) access.

//Gateway Route Config

@Configuration
public class GatewayConfig {

    private static final String SEGMENT = "/${segment}";

    @Bean
    public RouteLocator routeLocator(RouteLocatorBuilder builder) {
        return builder.routes()
                .route("microservice-a-route", r -> r.path("/microservice-a-service/**")
                        .filters(f -> f.rewritePath("/microservice-a-service/(?<segment>.*)", SEGMENT).tokenRelay())
                        .uri("lb://microservice-a"))
                .route("microservice-b-route", r -> r.path("/microservice-b-service/**")
                        .filters(f -> f.rewritePath("/microservice-b-service/(?<segment>.*)", SEGMENT).tokenRelay())
                        .uri("lb://microservice-b"))
                .build();
    }
}

// Gateway Security Config

@Bean
    public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http, ReactiveClientRegistrationRepository clientRepository) {

        http
                .authorizeExchange(authorize -> authorize
                        .pathMatchers("/actuator/**").permitAll()
                        //.pathMatchers("/user-service/api/public/**").permitAll()
                        .anyExchange().authenticated())
                .oauth2Login(login -> login.authorizationRequestResolver(pkceResolver(clientRepository)))
                .oauth2Client(Customizer.withDefaults());

        return http.build();
    }


private ServerOAuth2AuthorizationRequestResolver pkceResolver(ReactiveClientRegistrationRepository clientRepository) {
        var resolver = new DefaultServerOAuth2AuthorizationRequestResolver(clientRepository);
        resolver.setAuthorizationRequestCustomizer(OAuth2AuthorizationRequestCustomizers.withPkce());
        return resolver;
    }
//Microservice A security config
@Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http, OAuth2AuthorizedClientRepository authClientRepo) throws Exception {
        http
                .authorizeHttpRequests(auth -> auth
                        .requestMatchers("/api/public/**").permitAll()
                        .requestMatchers("/admin/**").hasRole("ADMIN")
                        .anyRequest().authenticated()
                )
                .oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults())) // Token validation
                .oauth2Client(client -> client.authorizedClientRepository(authClientRepo)); // Ensures token relay for Feign
        return http.build();
    }
}

So far I have tried different variations of pathMatchers/requestMatchers to set permitAll for the path. And also for testing purpose in Gateway Security Config I setup anyExchange().permitAll() but that also didn't helped.


Solution

  • You should have permitAll at two places: on the gateway and in resource servers serving public resources.

    Downstream REST resources access control should be managed by downstream resource servers => define these routes with permitAll on the gateway (all of it: the gateway shouldn't have to care about what resources are public).

    Public resources should be con figured with permitAll on resource servers too, just for the resource that should be accessible with an anonymous request.