How to use the Spring Cloud Gateway Filter for Authentication

In this article I will show the usage of the Spring Cloud Gateway filters for the authentication. I will start creating and describing a Spring Cloud Gateway project, what are the filters and how they work. Then, I will use them to perform the authentication of my microservices architecture.

Content:

  • The API Gateway design pattern
  • Routes and Predicates
  • Gateway filter
  • requests to the microservices from the Gateway filter with the Spring Cloud Netflix Ribbon Load Balancer.

For more details, watch this video.

Check this repository for all the code used in the article.

The API Gateway design pattern

In a previous article I’ve implemented a backend-user, which was another microservice, but with public access. This microservice was a web application, whose main role was to build a complete response calling the private microservices; and return the complete response to the user. But this solution isn’t a good one. Having a microservice to build a complete response calling the inner microservices will lead to a more complex system. Because, with time, instead of adding the complexity to the adequate microservice, i will tend to add it to this backend. It will be first some mapping, then some conversions, then some calculations, and finally, you have your spaghetti code.

Backend which dispatch requests manually
Backend which dispatch requests manually

This is why the API Gateway is a better solution for this case. The API Gateway will just redirect the incoming request to the inner microservices. It should have no more logic than that. It should have no more complexity than that. Then, the inner microservices, each one, will be responsible to deliver a complete response. Each microservice will need to communicate with the other to obtain the necessary information to build a complete response for the user.

Backend as an API Gateway
Backend as an API Gateway

Routes and Predicates

Let’s start by adding the maven dependency. And I must ensure to remove the web dependency.

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-gateway</artifactId>
    </dependency>

I will redirect all the requests which start by “books” to the books microservice; and all the requests which start by “prices” to the prices microservice.

spring:
  application.name: backend-user
  cloud:
    gateway:
      discovery.locator.enabled: true
      routes:
        - id: service-books-id
          uri: lb://service-books
          predicates:
            Path=/books/**
          filters:
            - AuthFilter
        - id: service-prices-id
          uri: lb://service-prices
          predicates:
            Path=/prices/**
          filters:
            - AuthFilter
        - id: service-users-sign-in
          uri: lb://service-users
          predicates:
            Path=/users/signIn

This option spring.cloud.gateway.discovery.locator.enabled will let me use the name of the microservices as they are registered in the Eureka Service Discovery. With the prefix “lb“, I use the Spring Cloud Netflix Ribbon Load Balancer to distribute the request to the microservice.

Within the predicate, I specify the conditions which make the request use this route. And as condition, I will only use the URL path with a regex, a start condition on the URL.

One last thing, the Gateway has a different way to be monitored, instead of using the regular Actuator configuration.

management:
  endpoint.gateway.enabled: true
  endpoints.web.exposure.include: gateway

With the configuration, i can handle multiple cases with different predicates; and multiple predicates for a single route. But let’s implement something more difficult. Let’s intercept each request and validate it’s really authenticated before continuing the routing. For that, i will use a custom filter, a Gateway Filter.

Gateway Filter

The Gateway Filters work as the HTTP Filters. They will intercept the incoming requests, I can add all the logic I want, then, let the requests continue to their destinations. And when finished, I can again add some logic to the returned response.

I will first add the Reactive Web Client, to allow me perform requests to other microservices. This client will read the information from the Eureka Service Discovery to map the services name with their IPs and ports.

@Configuration
public class WebClientConfig {

    @Bean
    @LoadBalanced
    public WebClient.Builder loadBalancedWebClientBuilder() {
        return WebClient.builder();
    }
}

Let’s now create the Gateway Filter.

@Component
public class AuthFilter extends AbstractGatewayFilterFactory<AuthFilter.Config> {

    private final WebClient.Builder webClientBuilder;

    public AuthFilter(WebClient.Builder webClientBuilder) {
        super(Config.class);
        this.webClientBuilder = webClientBuilder;
    }

    @Override
    public GatewayFilter apply(Config config) {
        return (exchange, chain) -> {
            if (!exchange.getRequest().getHeaders().containsKey(HttpHeaders.AUTHORIZATION)) {
                throw new RuntimeException("Missing authorization information");
            }

            String authHeader = exchange.getRequest().getHeaders().get(HttpHeaders.AUTHORIZATION).get(0);

            String[] parts = authHeader.split(" ");

            if (parts.length != 2 || !"Bearer".equals(parts[0])) {
                throw new RuntimeException("Incorrect authorization structure");
            }

            return webClientBuilder.build()
                    .post()
                    .uri("http://service-users/users/validateToken?token=" + parts[1])
                    .retrieve().bodyToMono(UserDto.class)
                    .map(userDto -> {
                        exchange.getRequest()
                                .mutate()
                                .header("X-auth-user-id", String.valueOf(userDto.getId()));
                        return exchange;
                    }).flatMap(chain::filter);
        };
    }

    public static class Config {
        // empty class as I don't need any particular configuration
    }
}

I will add the empty class Config as i don’t need any particular configuration.

webClientBuilder.build().post().uri() will make the request to the service-users microservice. And I read the response into to the UseDto class with retrieve().bodyToMono(). And with exchange().getRequest().mutate(), I edit the request adding a new header, a header with the user ID information, which will be sent to the target microservice. And, as with the HTTP filter, call the filter chain at the end.

With the last rule of the configuration, I redirect all the signIn routes to the service-users microservice. This way, I get my authentication information. The following requests must be made with the authentication information, the Bearer header, and the user information will be intercepted by the AuthFilter.

Conclusion

  • I’ve added the Spring Cloud Gateway dependency to handle the API Gateway pattern;
  • I’ve configured the routes adding the target microservice and the predicates;
  • To target the microservices, I’ve used Netflix Ribbon Load Balancer;
  • And I’ve created the Gateway Filter, AuthFilter, which intercepts all the requests.

Repository


Never Miss Another Tech Innovation

Concrete insights and actionable resources delivered straight to your inbox to boost your developer career.

My New ebook, Best Practices To Create A Backend With Spring Boot 3, is available now.

Best practices to create a backend with Spring Boot 3

One response to “How to use the Spring Cloud Gateway Filter for Authentication”

Leave a comment

Discover more from The Dev World - Sergio Lema

Subscribe now to keep reading and get access to the full archive.

Continue reading