Spring Cloud Gateway Security with JWT (JSON Web Token)

Spring Cloud Gateway Security

In this tutorial I am going to show you an example on Spring Cloud Gateway Security with JWT. JSON Web Tokens (JWT) are an open, industry standard RFC 7519 method for representing claims securely between two parties. JWT.IO allows you to decode, verify and generate JWT.

The Spring Cloud Gateway sits in front of your microservices and receives requests from clients and redirect those requests to appropriate microservices. It is customary to add a security layer here for restricting to the unauthorized requests which are coming from clients.

All that everything that you work with sensitive user data, such as emails, phone numbers, addresses, credit cards, etc. are exposed to the Internet should be secured.

Another scenario occurs when you have lots of microservices and it is very difficult to maintain the specific port for each of the microservices, so you want to start microservices on random ports, then Spring Cloud Gateway is the solution to access those microservices.

In this example on securing API gateway I am going to show you how to authenticate a user using JWT but if you need to apply authorization based on user’s role then you can also check my other tutorial on how to apply Authentication and role based Authorization to secure your API using JWT.

Authentication/Authorization Flow

If a client makes a request to some secured resources with no authentication/authorization, then API Gateway rejects it and redirects the user to the Authorization Server to authorize himself in the system. Therefore the client has to get all the required grants and then make the request again with the grants to receive information from the secured resources.

  1. User signup at end-point /register with username, password and role(s).
  2. The user information are stored into database.
  3. User logs in at end-point /login using the username and password, which user used at step 1.
  4. User receives JWT (JSON Web Token) on successful login.
  5. User continues to access the end-points for which user has role(s) as long as the token is valid. User must send JWT in HTTP header with key/value as Authorization <generated JWT on signin>.
spring cloud gateway security with jwt

Prerequisites

Java 11/19, Maven 3.6.3 – 3.8.5, Spring Boot 2.5.0-2.6.6/3.0.2, Spring Cloud 2020.0.2 – 2021.0.1/2022.0.1, JWT 0.9.1/0.11.5, Postman

Project Setup

Create a maven based microservices in your favorite IDE or tool. Check the Source Code section for downloading projects.

Authentication Service

This authentication/authorization service will provide the JWT token if the user data exists in the server. This jwt token will be used for accessing further the secured API endpoints.

In this service the important part here is the JWT token which is generated based on the user’s credentials. For this example I am using just and id (or user id) that can be used to generate the JWT token. In ideal scenario you need to use more parameter to make the token more complex.

@Component
public class JwtUtil {

	@Value("${jwt.secret}")
	private String jwtSecret;

	@Value("${jwt.token.validity}")
	private long tokenValidity;

	public Claims getClaims(final String token) {
		try {
			Claims body = Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(token).getBody();
			return body;
		} catch (Exception e) {
			System.out.println(e.getMessage() + " => " + e);
		}
		return null;
	}

	public String generateToken(String id) {
		Claims claims = Jwts.claims().setSubject(id);
		long nowMillis = System.currentTimeMillis();
		long expMillis = nowMillis + tokenValidity;
		Date exp = new Date(expMillis);
		return Jwts.builder().setClaims(claims).setIssuedAt(new Date(nowMillis)).setExpiration(exp)
				.signWith(SignatureAlgorithm.HS512, jwtSecret).compact();
	}

	public void validateToken(final String token) throws JwtTokenMalformedException, JwtTokenMissingException {
		try {
			Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(token);
		} catch (SignatureException ex) {
			throw new JwtTokenMalformedException("Invalid JWT signature");
		} catch (MalformedJwtException ex) {
			throw new JwtTokenMalformedException("Invalid JWT token");
		} catch (ExpiredJwtException ex) {
			throw new JwtTokenMalformedException("Expired JWT token");
		} catch (UnsupportedJwtException ex) {
			throw new JwtTokenMalformedException("Unsupported JWT token");
		} catch (IllegalArgumentException ex) {
			throw new JwtTokenMissingException("JWT claims string is empty.");
		}
	}

}

The following REST controller class that exposes two endpoints for register and login. For this example I am not storing the client information anywhere but if your real project you must save the information for validating the user on subsequent requests.

@RestController
public class AuthRestController {

	@Autowired
	private JwtUtil jwtUtil;

	@PostMapping("/auth/login")
	public ResponseEntity<String> login(@RequestBody String userName) {
		String token = jwtUtil.generateToken(userName);

		return new ResponseEntity<String>(token, HttpStatus.OK);
	}

	@PostMapping("/auth/register")
	public ResponseEntity<String> register(@RequestBody String userName) {
		// Persist user to some persistent storage
		System.out.println("Info saved...");

		return new ResponseEntity<String>("Registered", HttpStatus.OK);
	}

}

Spring Cloud Gateway

The spring cloud gateway acts as a gate keeper that accepts/rejects the requests from clients based on the criteria configured in the gateway.

The important part in the gateway is the filter that performs the validation on the incoming requests and route the requests to the appropriate microservices.

In the below source code I have bypassed the endpoints /register and /login from security check. So these endpoints can be easily accessible without providing any authority.

@Component
public class JwtAuthenticationFilter implements GatewayFilter {

	@Autowired
	private JwtUtil jwtUtil;

	@Override
	public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
		ServerHttpRequest request = (ServerHttpRequest) exchange.getRequest();

		final List<String> apiEndpoints = List.of("/register", "/login");

		Predicate<ServerHttpRequest> isApiSecured = r -> apiEndpoints.stream()
				.noneMatch(uri -> r.getURI().getPath().contains(uri));

		if (isApiSecured.test(request)) {
			if (!request.getHeaders().containsKey("Authorization")) {
				ServerHttpResponse response = exchange.getResponse();
				response.setStatusCode(HttpStatus.UNAUTHORIZED);

				return response.setComplete();
			}

			final String token = request.getHeaders().getOrEmpty("Authorization").get(0);

			try {
				jwtUtil.validateToken(token);
			} catch (JwtTokenMalformedException | JwtTokenMissingException e) {
				// e.printStackTrace();

				ServerHttpResponse response = exchange.getResponse();
				response.setStatusCode(HttpStatus.BAD_REQUEST);

				return response.setComplete();
			}

			Claims claims = jwtUtil.getClaims(token);
			exchange.getRequest().mutate().header("id", String.valueOf(claims.get("id"))).build();
		}

		return chain.filter(exchange);
	}

}

Next I will show you how to route requests to appropriate microservices. Here I am using load balancing instead of host and port to access the microservices through gateway. For each route I am using the authentication filter so that the security gets applied as appropriately.

@Configuration
public class GatewayConfig {

	@Autowired
	private JwtAuthenticationFilter filter;

	@Bean
	public RouteLocator routes(RouteLocatorBuilder builder) {
		return builder.routes().route("auth", r -> r.path("/auth/**").filters(f -> f.filter(filter)).uri("lb://auth"))
				.route("alert", r -> r.path("/alert/**").filters(f -> f.filter(filter)).uri("lb://alert"))
				.route("echo", r -> r.path("/echo/**").filters(f -> f.filter(filter)).uri("lb://echo"))
				.route("hello", r -> r.path("/hello/**").filters(f -> f.filter(filter)).uri("lb://hello")).build();
	}

}

Note I have created JwtUtil class in both places – Auth Service and Spring Cloud Gateway, and I have also used the placeholder ${jwt.secret} that gets resolved through Spring’s @Value annotation. So this should ideally be configured through Spring Cloud Bus so that any change to this value will be reflected in all other services.

I have also created several other microservices such as, alert, echo and hello. I have also used Eureka server for service discovery purpose. You can download the whole source code from the Source Code location later.

All services when up and running you will find them on the Eureka service discovery page (http://localhost:8761/):

spring cloud gateway security with jwt

In the above image under the Status column you only find IP address of the services without port, because I have forced the application to display only IP address (though you will find the port while you mouse hover the IP address link under Status column) instead of hostname by setting the following line in the application.properties file:

eureka.instance.instance-id=

If you did not set the above line with empty value, the Status column would have displayed with hostname:port format, where hostname is the Desktop or hostname of your machine.

If you want to display IP:port format then you need to set the above line as follows:

eureka.instance.instance-id={server.address}:{server.port}

where {server.address} is the value you need to set in the application.properties file.

Testing JWT Auth in Spring Cloud Gateway

Make sure you start all of your services including Eureka and Gateway.

To get the JWT token use the following details in Postman tool:

  • HTTP Method: POST
  • URL: http://localhost:8080/auth/login
  • Body: raw -> JSON
{
   "id": "Soumitra"
}

Click on the Send button in the Postman tool and you will get the JWT token:

spring cloud gateway security with jwt

While you are calling the secured microservices with valid JWT token then you will see the expected output from the service. For example, let’s test for the alert service:

  • HTTP Method: GET
  • URL: http://localhost:8080/alert/alert
  • Headers: KEY -> Authorization, VALUE -> eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJ7XHJcbiAgIFwiaWRcIjogXCJTb3VtaXRyYVwiXHJcbn0iLCJpYXQiOjE2NDk4MjkzMzMsImV4cCI6MTY0OTgyOTUxM30.oT7wWcMRkVxxmAhrOrcQ9QoLXOjh2bj0q0P3ARfQmQdS-gNz2us6DfiDLcJX2O-_8E_1ub4zUzvE3jSnOA0FKA
spring cloud gateway security with jwt

If you are trying to access the microservice without having the Authorization token in header then you will see Unauthorized error:

spring cloud gateway security with jwt

If your JWT token gets expired and you are trying to access the service then you will see the following error:

spring cloud gateway security with jwt

That’s all about the example about Spring Cloud Gateway security with JWT (JSON Wen Token).

Note

The following two lines in the application.properties file have been added to avoid the issue similar to the java.net.UnknownHostException: Failed to resolve 'abc.xyz.com' after 2 queries:

eureka.instance.prefer-ip-address=true
eureka.instance.hostname=localhost

Source Code

Download

Download For Spring Boot 3

4 thoughts on “Spring Cloud Gateway Security with JWT (JSON Web Token)

  1. I got java.lang.NoClassDefFoundError: javax/xml/bind/DatatypeConverter
    Even after adding dependency also

  2. I followed your code. Upon making a request to my auth server, I get ‘An expected CSRF token cannot be found’.

Leave a Reply

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