Implement Basic Authentication in Spring Cloud Gateway using Custom Filter
In this tutorial, we will explore how to implement basic authentication in Spring Cloud Gateway using a custom filter. Spring Cloud Gateway is a powerful tool for building API gateways that can handle various cross-cutting concerns like security, monitoring, and routing. Basic authentication is a simple and widely used method for protecting web resources but adding basic authentication ensures that only authorised users can access your services through the gateway. Implementing basic authentication in Spring Cloud Gateway enables you to centralize authentication logic and enforce security policies across multiple services.
In this article, we are implementing basic authentication without using Spring Security. However, if you prefer to implement basic authentication in Spring Cloud Gateway using Spring Security, you can find detailed instructions by clicking here.
Implementing Basic Authentication using custom filter involves several steps. Below are the basic steps to set it up:
1. Create a Spring Cloud Gateway Project:
If you do not currently have a Spring Cloud Gateway project, initiate its creation using Spring Boot. You have the option to use the Spring Initializer website, Spring Boot CLI, or the Spring Boot initializer plugin within your preferred IDE.
2. Add Dependencies:
Ensure you have Spring Cloud Gateway starter dependency in your pom.xml
or build.gradle
file.
<dependencies> <!-- Spring Cloud Gateway --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency> <!-- Add other dependencies as per the requirement --> </dependencies>
Changes in your build.gradle file as shown below.
dependencies { implementation 'org.springframework.cloud:spring-cloud-starter-gateway' }
You may include additional dependencies as needed.
3. Create Custom Filter:
Firstly, let’s understand how the client prepares and sends the credentials in the Authorization header, after which we’ll delve into the server-side aspect.
Client side processing:
- The client/consumer acquires the username and password.
- Create a string by combining the username and password using a colon as a delimiter, as shown in the following format:
<username>:<password>
- Encode a string using Base64 encoding.
- After encoding, the encoded string is prefixed with “Basic“.
- The resulting string is placed within the Authorization header. The encoded credential, in its final format, appears as follows.
Authorization: Basic dGVzdC11c2VyOnRlc3QtcHdk
- Subsequently, the client sends an HTTP request to access a protected resource along with credentials.
Server side processing:
- After the request reaches the server, it proceeds to verify and validate the credentials.
- If the credentials are valid, the server processes the request and returns the requested resource along with an HTTP status code 200 (OK).
- If the credentials are invalid, the server returns an HTTP status code 401 (Unauthorized).
To handle the server side validation process, let’s create a new class called BasicAuthGatewayFilter that extends Spring’s AbstractGatewayFilterFactory abstract class. This filter will intercept incoming requests and check for basic authentication credentials.
@Component public class BasicAuthGatewayFilter extends AbstractGatewayFilterFactory<BasicAuthGatewayFilter.Config> { private static final String EMPTY_STR = ""; private static final String COLON_STR = ":"; private static final String BASIC_STR = "Basic "; @Override public GatewayFilter apply(Config config) { return (exchange, chain) -> { if (exchange.getRequest().getHeaders().containsKey(HttpHeaders.AUTHORIZATION)) { String basicAuthValue = exchange.getRequest().getHeaders().getFirst(HttpHeaders.AUTHORIZATION); basicAuthValue = basicAuthValue != null ? basicAuthValue.replace(BASIC_STR, EMPTY_STR) : EMPTY_STR; basicAuthValue = new String(Base64.getDecoder().decode(basicAuthValue.getBytes())); String[] credentials = basicAuthValue.split(COLON_STR); if (credentials.length == 2) { String userName = credentials[0]; String password = credentials[1]; //Check credentials with difference sources like database, LDAP, static files, etc //For demonstration purpose, here we are validating credentials with hard code values. if (userName.equals("test-user") && password.equals("test-pwd")) { return chain.filter(exchange); } } } exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED); return exchange.getResponse().setComplete(); }; } @Override public Class<Config> getConfigClass() { return Config.class; } static class Config { } }
In the above example,
Within the apply
method of the filter, we are implementing the logic to extract and validate the Basic Authentication credentials.
Here, we are verifying the existence of the Authorization header in the request. If found, we extract the credentials from the header. To obtain the final encoded string, we remove “Basic” prefix added by the client. Once we have the actual encoded string, which is encoded using Base64 encoding, we decode it using a Base64 decoder. The resulting decoded string is in the following format:
<username>:<password>
The username and password are delimited by a colon (“:”).
By splitting a string, we extract the actual username and password sent by the client/consumer..
As we obtain the username and password, we have the capability to verify credentials against various data sources such as databases, LDAP, or static files. In the example, we have used hardcoded values solely for demonstration purposes.
If the credentials are valid, the filter will proceed to pass the request to the next resource in the chain. Consequently, the requested resource, accompanied by an HTTP status code 200 (OK), is returned to the client.
If the credentials are invalid, the filter will respond with an HTTP status code 401 (Unauthorized). Below is the code used to set the HTTP status code 401 and return the request to the client.
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED); return exchange.getResponse().setComplete();
The code above simply returns a blank response with a 401 HTTP status code. However, if you wish to customize the unauthorized message in the response, you can include the following method within the BasicAuthGatewayFilter class and call this method from apply method of this class.
private Mono<Void> returnUnAuthorizedMessage(ServerWebExchange exchange) { ServerHttpResponse response = exchange.getResponse(); response.setStatusCode(HttpStatus.UNAUTHORIZED); response.getHeaders().setContentType(MediaType.APPLICATION_JSON); String data = "{\"code\":" + HttpStatus.UNAUTHORIZED + ", \"message\":\"You are unable to authorize.\"}"; DataBuffer buffer = response.bufferFactory().wrap(data.getBytes()); return exchange.getResponse().writeWith(Mono.just(buffer)); }
The code above configures the HTTP status code to 401, sets the JSON content type, and defines a custom response message.
4. Configuration:
Configure your Spring Cloud Gateway to use the custom authentication filter. This can be done in the application’s configuration file (application.yml or application.properties).
spring: cloud: gateway: routes: - id: post_service uri: http://localhost:8081 # Replace with your target URI predicates: - Path=/post/* # Replace with your endpoints filters: - BasicAuthGatewayFilter # Here we have configured our basic auth filter
If you wish to exclude certain endpoints from authentication requirements, simply refrain from adding the authentication filter to their routing configuration.
Ensure to substitute http://localhost:8081 with the appropriate URI of your backend service and adjust other configurations as necessary to suit your specific requirements.
Note: In this example, we have developed a post service and are directing traffic to it via the gateway. You will locate its source code alongside the tutorial’s source.
To access the full source code for this tutorial, please click here.
5. Testing:
Up to this point, we have implemented the filter and completed the required configuration. Now, let’s proceed to start both the post service and the Spring Cloud Gateway.
We are going to test the endpoint using Postman.
Open the Postman app & put the REST endpoint in the address bar as shown below.
In above image, we have put REST endpoint URL & click on send button, status code 401 unauthorised
is returned from the api gateway. Here we have not specified any authentication method in Authorization tab of Postman , hence we got this status code.
Now lets specify the basic authentication along with username & password in Authorization tab.
Here, we have set Basic Authentication method along with username & password and clicked send button. The service returned 200 OK
status code with the response in json format.
Note: We have highlighted the necessary points in the above images.
The entire source code for this article can be found here
Conclusion:
In this article we have outlined various steps which involves creating a custom filter that intercepts incoming requests, validates the credentials provided, and grants access accordingly.
Implementing basic authentication in Spring Cloud Gateway using a custom filter provides a flexible way to secure your microservices architecture. By intercepting incoming requests, you can authenticate users before allowing access to protected resources. This ensures the security of your application and its endpoints.
Leave a Reply