Search
Close this search box.
spring cloud gateway

Mastering API Gateway with Spring Boot and Spring Cloud Gateway: A Comprehensive 15-Step Guide for Developers

Introduction

API gateways have become a crucial component of modern microservices architecture, providing a single entry point for clients to access multiple services. It is a popular choice among developers for building scalable, secure, and maintainable API gateways. In this guide, we will take you through the step-by-step process of mastering API gateway development with Spring Cloud Gateway. We will also develop an example code while going through this article.

What is an API Gateway?

An API gateway acts as a single entry point for clients to access multiple services, providing features such as load balancing, rate limiting, and caching. The security layer is provided by filtering incoming requests, validating authentication credentials, and encrypting sensitive data.

api gateway architecture

Benefits of Using Spring Cloud Gateway

It offers several benefits, including:

  • Scalability: Designed to handle heavy traffic loads with ease and deliver exceptional performance, even during peak times
  • Security: Provides robust features for authentication, authorization, and encryption
  • Flexibility: Supports multiple routing strategies and filters
  • Maintainability: Easy to configure and deploy

Prerequisites

Before you begin, ensure that:

  • You have a foundational understanding of Java and Spring Boot. If not please read my articles on Spring Boot
  • You have Maven or Gradle installed on your system. You can read my article on Maven if you are new to Maven here
  • You have a IDE (e.g. Eclipse, IntelliJ IDEA) with Spring Tool Suite

Step-by-Step Tutorial: Building a Simple API Gateway with Spring Cloud Gateway

Step 1: Create a New Spring Boot Project

Open your IDE and create a new Spring Boot project using the following command:

spring init --build=maven --name=my-api-gateway

Alternatively, you can use the Spring Initializr web interface to create a new project.

Step 2: Add Dependencies to Your ‘pom.xml’ File

Add the following dependencies to your `pom.xml` file to include Cloud Gateway in your project:

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

Step 3: Configure it in Your ‘application.yml’ File

Configure the spring cloud gateway settings in your `application.yml` file as follows:

spring:
  application:
    name: spring-cloud-gateway
  cloud:
    gateway:
      routes:
        - id: hello-world-service
          uri: https://hello-world-service.com/
          predicates:
            - Path=/hello/**

Step 4: Create a Route Configuration Class

Create a new class, ‘HelloWorldRouteConfiguration.java’, to configure the route for your service:

package com.aneesh.spring.cloud.gateway;

import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;

public class HelloWorldRouteConfiguration {
    @Bean
    public RouteLocator routes(RouteLocatorBuilder builder) {

        return builder.routes()
                .route("hello-world-service", r -> r.path("/hello/**")
                        .uri("https://hello-world-service.com/"))
                .build();
    }
}

Step 5: Run Your Application

Run your application using the following command:

mvn spring-boot:run

You should now be able to access your service by visiting `http://localhost:8080/hello` in your browser.

Understanding Route and Filter

In Spring Cloud Gateway, routes are defined as a combination of predicates and filters. Predicates determine which incoming requests should be routed to a specific filter, while filters perform actions

on the request before forwarding it to the downstream service.

Step 6: Create a Custom Filter

package com.aneesh.spring.cloud.gatewa;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.stereotype.Component;

import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;


@Component

public class CustomFilter implements GlobalFilter {
    final Logger logger =
            LoggerFactory.getLogger(AuthFilter.class);

    @Override
    public Mono<Void> filter(
            ServerWebExchange exchange,
            GatewayFilterChain chain) {
        logger.info("Global Pre Filter executed");
        return chain.filter(exchange);
    }
}

Step 7: Apply the Filter to Your Route

Apply the filter to your route in the `application.yml` file:

spring:
  application:
    name: spring-cloud-gateway
  cloud:
    gateway:
      routes:
        - id: hello-world-service
          uri: https://hello-world-service.com/
          predicates:
            - Path=/hello/**
          filters:
            -name: CustomFilter

Handling Errors and Logging

Spring Cloud Gateway provides built-in support for handling errors and logging. You can use the `ErrorResponse` class to return a custom error response, and the `LoggingGatewayFilterFactory` class to log

incoming requests.

Step 8: Create an Error Response Class

Create a custom error response class:

package com.aneesh.spring.cloud.gatewa;

import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatusCode;
import org.springframework.http.ResponseCookie;
import org.springframework.util.MultiValueMap;
import org.springframework.web.reactive.function.server.ServerResponse;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

public class ErrorResponse implements ServerResponse {

    private int status;

    private String message;

    public ErrorResponse(int status) {

        this.status = status;

    }

    public ErrorResponse(int status, String message) {

        this.status = status;

        this.message = message;

    }

    public int getStatus() {
        return status;
    }

    public void setStatus(int status) {
        this.status = status;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    @Override
    public HttpStatusCode statusCode() {
        return HttpStatusCode.valueOf(status);
    }

    @Override
    public int rawStatusCode() {
        return status;
    }

    @Override
    public HttpHeaders headers() {
        return HttpHeaders.readOnlyHttpHeaders(HttpHeaders.EMPTY);
    }

    @Override
    public MultiValueMap<String, ResponseCookie> cookies() {
        return null;
    }

    @Override
    public Mono<Void> writeTo(ServerWebExchange exchange, Context context) {
        return null;
    }
}

Step 9: Apply the Logging Filter to Your Route

Create a  GatewayFilter Factory which will log before the request is forwarded and after it is executed

package com.aneesh.spring.cloud.gatewa;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;

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

    final Logger logger =
            LoggerFactory.getLogger(LoggingGatewayFilterFactory.class);

    public LoggingGatewayFilterFactory() {
        super(Config.class);
    }

    @Override
    public GatewayFilter apply(Config config) {
        return (exchange, chain) -> {
            // Pre-processing

            if (config.preLogger) {
                logger.info("Pre LoggingGatewayFilter message: "
                        + config.preMessage + "request from:"+exchange.getRequest().getRemoteAddress());
            }
            return chain.filter(exchange)
                    .then(Mono.fromRunnable(() -> {
                        // Post-processing
                        if (config.postLogger) {
                            logger.info("Post LoggingGatewayFilter message: "
                                    + config.postMessage+ "response headers: "
+exchange.getResponse().getHeaders().getAccept());
                        }
                    }));
        };
    }

    public static class Config {
        private String preMessage;
        private boolean preLogger;
        private String postMessage;
        private boolean postLogger;
    }
}

Apply the logging filter to your route in the `application.yml` file:

spring:
  application:
    name: spring-cloud-gateway
  cloud:
    gateway:
      routes:
        - id: hello-world-service
          uri: https://hello-world-service.com/
          predicates:
            - Path=/hello/**
          filters:
            -name: LoggingGatewayFilterFactory
            args:
              preMessage: before logging
              preLogger: true
              postMessage: after logging
              postLogger: true

Securing Your API Gateway with OAuth 2.0 and JWT

To secure your API gateway using OAuth 2.0 and JWT, you can use the `spring-cloud-starter-oauth2` dependency to include OAuth 2.0 support in your project.

Step 10: Configure OAuth 2.0 Settings

Configure the OAuth 2.0 settings in your `application.yml` file:

spring:
  application:
    name: spring-cloud-gateway
  cloud:
    oauth2:
      client:
        registration:
          my-client:
            clientId: my_client_id
            clientSecret: my_client_secret
            redirectUri: https://my-redirect-uri.com/

Step 11: Create an OAuth 2.0 Filter

Create a custom filter to validate authentication credentials using OAuth 2.0:

package com.aneesh.spring.cloud.gatewa;

import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

@Component
public class AuthFilter implements GlobalFilter {

    @Override
    public Mono<> filter(ServerWebExchange exchange, GatewayFilterChain  chain)  {
        String authHeader = exchange.getRequest().getHeaders().getFirst("Authorization");
        // Validate authentication credentials here
        if (authHeader == null || !authHeader.startsWith("Bearer ")) {
           return Mono.just(new ErrorResponse(401));
        }
        // Validate authentication credentials here using OAuth 2.0
        return chain.filter(exchange);
    }

}

Step 12: Implementing Spring Cloud gateway Rate Limiting**

To add rate limiting to our API gateway, we’ll use a library called Spring Cloud Gateway’s `RateLimiter` feature. Create a new file called `RateLimitConfig.java` with the following code:

package com.aneesh.spring.cloud.gatewa;

import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import org.springframework.cloud.gateway.filter.factory.RateLimiterFactory;

@Component
public class RateLimitConfig {
    @Bean
    public RateLimiterFactory rateLimiterFactory() {
        return new RateLimiterFactory(10, 60);
    }

}

In this configuration, we’ve enabled a rate limiter that allows up to 10 requests per minute for each user.

Deploying the application to Production

To deploy your API gateway to production, you can use a containerization tool such as Docker to package and deploy your application.

Step 12: Create a Dockerfile

Create a `Dockerfile` in the root of your project:

FROM openjdk:8-jdk-alpine
WORKDIR /app
COPY . .
RUN mvn clean package
EXPOSE 8080
CMD ["java", "-jar", "my-api-gateway.jar"]

Step 13: Build a Docker Image

Build a Docker image using the following command:

docker build -t my-api-gateway .

Step 14: Push the Image to a Container Registry

Push the image to a container registry such as Docker Hub or Amazon ECR.

docker tag appname dockerhubusername/appname
docker login
docker push dockerhubusername/appname

Step 15: Deploy Your Application to Production

Deploy your application to production by running the following command:

docker run -p 8080:8080 my-api-gateway

Spring Cloud Gateway vs Zuul

When it comes to building a robust API gateway for microservices, the choice between Spring Cloud Gateway and Zuul can be daunting. Consider this example of routing traffic to multiple services using Gateway’s

@Bean
public Route routes() {
    return new RouteBuilder()
        .route("service-a", r -> r.request().uri("http://localhost:8081"))
        .route("service-b", r -> r.request().uri("http://localhost:8082"))
        .build();
}

Compare this to Zuul’s more verbose configuration using a ZuulProperties bean:

zuul:
  routes:
    service-a:
      path: /api/**
      serviceId: service-a

As you can see, Gateway makes it much easier to define and manage routes.

Another key difference between Cloud Gateway and Zuul lies in their handling of filters. In Gateway, you can use a FilterFactoryBean as mentioned above to create and apply filters like this:

@Bean
public FilterFactoryBean loggingFilter() {
    return new FilterFactoryBean()
        .addFilter(new LoggingFilter());
}

Zuul, on the other hand, uses a pre and post filter mechanism that’s more cumbersome to work with. For example:

zuul:
  filters:
    pre:
      - logging.filter1
      - logging.filter2

As you can see, Gateway makes it much simpler to apply filters to your API requests.

Congratulations! You have now successfully mastered API gateway development with Spring Cloud Gateway.

FAQ

Q: What is the difference between a traditional proxy server and an API gateway?

A: A traditional proxy server primarily forwards incoming requests to a backend service, while an API gateway provides a layer of abstraction between clients and services. It handles features such as

authentication, rate limiting, caching, and error handling.

Q: How does Spring Cloud Gateway handle load balancing?

A: Spring Cloud Gateway uses the Hystrix library for load balancing. You can configure multiple instances of the same service to be used for load-balancing purposes. This ensures that your application remains available even if one instance fails or becomes unresponsive.

Q: Can I use Spring Cloud Gateway with other web frameworks such as Jersey?

A: Yes, you can use Spring Cloud Gateway with other web frameworks like Jersey. Spring Cloud Gateway provides a flexible architecture that allows integration with multiple web frameworks through its filters and gateway interfaces.

Q: How do I handle errors in my API gateway using Spring Cloud Gateway?

A: You can create custom error handlers to return specific error responses based on the type of error encountered. Spring Cloud Gateway also provides built-in support for handling errors, such as returning HTTP status codes and including error messages in response bodies.

Q: Is it possible to secure my API gateway with OAuth 2.0 and JWT?

A: Yes, you can use the spring-cloud-starter-oauth2 dependency to include OAuth 2.0 support in your Spring Cloud Gateway application. This allows for secure authentication of clients using tokens issued by an authorization server. You can then use these tokens to validate client identities in subsequent requests.

Q: What’s the main difference between an API Gateway and a Load Balancer?

A: API Gateway vs Load Balancer is the most common question among microservice developers. The answer is while both are used to manage traffic, an API Gateway is like a concierge who greets you at the door of your favorite restaurant, while a Load Balancer is more like a bouncer who lets in the right number of people. The Gateway handles requests, applies security and caching, and routes them to the right service, whereas the Load Balancer just spreads the load across multiple servers.

Share the post

Leave a Reply

Your email address will not be published. Required fields are marked *