Feign Client with Api Gateway
Many members of our springboot microservice community are encountering issues when communicating with microservices using Feign client. A common problem they face is the 401 Unauthorized error. In this blog post, we will explore how to resolve this issue.
Why your Feign Client API is failed
The above diagram provides a clear insight into why your API is encountering issues. When a service is requested from either your computer or mobile device through the gateway, with the appropriate credentials, the API directs this request to the relevant microservice — in this case, the school-library
microservice. All the necessary credentials for this transaction are stored within the school-library
microservice.
The complication arises when the school-library
microservice realizes it needs additional information from the student
microservice. In response, the school-library
microservice initiates an API call to the student
microservice through the gateway. However, the problem surfaces at this point.
The core issue lies in our attempt to retrieve data from the student
microservice through the gateway, which requires proper authorization. Unfortunately, our request lacks the necessary authorization information, leading to the failure of our application and the issuance of a 401 error code. This error stems from the absence of crucial authorization data in our communication with the gateway, disrupting the intended functionality of the API call.
How to resolve this issue
To address this issue, the recommended solution is to include authorization information in the API call initiated from the school-library
microservice to the student
microservice through the API gateway
. By incorporating the necessary authorization details, we ensure that the communication between these microservices via the gateway is authenticated, subsequently preventing the occurrence of the 401 error. This corrective action establishes a secure and authorized channel for the exchange of information, resolving the underlying problem in the microservice communication flow.
In the depicted scenario, it is evident that all communication channels between microservices and the gateway are now authenticated. This implementation ensures the security and legitimacy of the interactions. As a result, instead of encountering a 401 error response, we observe successful communication, indicated by a 200 response. The inclusion of authentication in the microservices-to-gateway communication has effectively resolved the previous issue, establishing a robust and secure framework for data exchange.
How to programmatically implement a solution for this problem?
First, ensure that you have properly implemented the Feign client in your application. Visit the below blog post where I have outlined how to implement the Feign client in your project.
As per my knowledge two ways you can fix this issue.
Solution 1 : Passing http headers to Feign Client
Solution 2 : Use of Request Interceptor (Recommended)
Passing http headers to Feign Client
Step 1 : In your controller get the HttpServletRequest
object like this.
@RestController
@RequestMapping(value = "/api/membership")
public class SchoolLibraryController {
@GetMapping(value = "/feign/{id}")
public ResponseEntity<ApiResponse<MembershipDto>> getStudentMembershipFeign(HttpServletRequest request, @PathVariable int id) {
return membershipService.getLibraryMembershipFeign(request, id);
}
}
Step 2: Create a method like below in your service layer to create header object.
private HttpHeaders getHeaders(final HttpServletRequest httpServletRequest) {
var iterator = httpServletRequest.getHeaderNames().asIterator();
final HttpHeaders headers = new HttpHeaders();
while (iterator.hasNext()) {
var key = iterator.next();
headers.add(key, httpServletRequest.getHeader(key));
}
return headers;
}
So the function inside your service layer look like this.
public ResponseEntity<ApiResponse<MembershipDto>> getLibraryMembership(HttpServletRequest request, int id) {
final HttpHeaders httpHeaders = getHeaders(request);
return null;
}
Step 3 : Now we have to call Feign client with this header.
See the sample code I have created for the Feign client.
@FeignClient(value = "api-gateway", path = "student-service/api/students/")
public interface StudentFeignClient {
@GetMapping("/{id}")
ApiResponse<Map<String,Object>> getStudentById(@RequestHeader HttpHeaders headers, @PathVariable int id);
}
Step 3: Now create a an another service layer to implement Circuit breaker, like below.
@Service
public class StudentService {
private static final Logger logger = LoggerFactory.getLogger(StudentService.class);
private final StudentFeignClient studentFeignClient;
@Autowired
public StudentService(final StudentFeignClient studentFeignClient) {
this.studentFeignClient = studentFeignClient;
}
@CircuitBreaker(name = "studentService", fallbackMethod = "getStudentByIdFallbackMethod")
public Object getStudentById(final HttpHeaders headers, final int id) {
logger.info("Student feign client to get student data");
return studentFeignClient.getStudentById(headers, id).getData().get("student");
}
public Object getStudentByIdFallbackMethod(final HttpHeaders headers, final int id, final Throwable th) {
logger.error("Failed to get student details", th);
return "Student details not found for student with id.. " + id + " No response from student server";
}
}
Step 4 (Final Step):
In the main service (the service injected into the controller), inject the service created in step 3 (use @Autowired
). Recommending constructor injection.
@Autowired
private StudentService studentService;
Now, you have to call the studentService
from your main service and return the response, as shown below:
public ResponseEntity<ApiResponse<MembershipDto>> getLibraryMembership(HttpServletRequest request, int id) {
final HttpHeaders httpHeaders = getHeaders(request);
final Object studentData = studentService.getStudentById(httpHeaders, id);
final ApiResponse response = ApiResponse.builder().data(studentData).build();
return ResponseEntity.ok(response);
}
Sample code with documentation is available at this link
Use of RequestInterceptor
In the context of Java development and microservice communication using Feign client through an API gateway with authorization, a RequestInterceptor
is a component that allows you to intercept and modify HTTP requests made by the Feign client before they are sent to the target service. This can be useful for various purposes, such as adding custom headers, modifying request parameters, or performing authentication and authorization tasks.
When working with Feign clients in a microservices architecture, using a RequestInterceptor
gives you the flexibility to customize and enhance the behavior of your HTTP requests based on specific requirements, ensuring seamless communication between microservices while adhering to security and authorization protocols.
How to configure RequestInterceptor?
Step 1: Create a subclass of RequestInterceptor
and configure the method as shown below.
@Component
public class AuthFeignInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate template) {
final RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
if (requestAttributes != null) {
final HttpServletRequest httpServletRequest = ((ServletRequestAttributes) requestAttributes).getRequest();
template.header(HttpHeaders.AUTHORIZATION, httpServletRequest.getHeader(HttpHeaders.AUTHORIZATION));
}
}
}
Step 2: You are done. No other changes are required in your Feign client (No header information). Make sure you have successfully configured the FeignClient, as shown below:
@FeignClient(value = "api-gateway", path = "student-service/api/students/")
public interface StudentFeignClient {
@GetMapping("/{id}")
ApiResponse<Map<String,Object>> getStudentById(@PathVariable int id);
}
Refer to the information in step 3 from the previous solution and create a service with a Feign client. The only difference is that you do not need to pass the header information. Like below
@Service
public class StudentService {
private static final Logger logger = LoggerFactory.getLogger(StudentService.class);
private final StudentFeignClient studentFeignClient;
@Autowired
public StudentService(final StudentFeignClient studentFeignClient) {
this.studentFeignClient = studentFeignClient;
}
@CircuitBreaker(name = "studentService", fallbackMethod = "getStudentByIdFallbackMethod")
public Object getStudentById(final int id) {
logger.info("Student feign client to get student data");
return studentFeignClient.getStudentById(id).getData().get("student");
}
public Object getStudentByIdFallbackMethod(final int id, final Throwable th) {
logger.error("Failed to get student details", th);
return "Student details not found for student with id.. " + id + " No response from student server";
}
}
Reference code
For more information about the implementation, please visit my GitHub page mentioned below.
https://github.com/DeepuGeorgeJacob/school-management/blob/main/docs/FeignClientJDBCUserSecurity.md
Conclusion
In conclusion, the decision between passing headers directly to a Feign client and utilizing a RequestInterceptor
in microservice communication hinges on the level of customization and flexibility required in handling HTTP requests. When specific headers need to be included on a per-request basis, the @RequestHeader
annotation offers a targeted approach within the Feign client interface. On the other hand, employing a RequestInterceptor
provides a centralized mechanism for modifying requests globally, offering a more streamlined and consistent solution, especially when multiple requests share common requirements. Both approaches empower Java developers to tailor microservice communication effectively, ensuring a balance between simplicity and granular control based on the unique needs of their architecture.
Please reach out to me on LinkedIn for more queries.