Forward remote IP address from Nginx to a Spring Boot application
This post explains how to forward the remote IP address of a client to a Spring Boot application when routing traffic through Nginx (reverse proxy).
Introduction
Currently, my preferred way to set up a Spring Boot based web application in production is to use a Reverse Proxy to route traffic to the corresponding backed applications. In this context, I really like Nginx as a fast and reliable web server with low memory footprint. The problem though, when using a reverse proxy is: when requests are forwarded to the backend applications, the remote IP address of the client is being replaced by the IP address of the reverse proxy server (e.g. 127.0.0.1 when installed on the same server).
To log requests and analyze user and behavioral patterns, it is beneficial to be able to use the remote IP address. This post explains how this can be achieved easily using Nginx.
1. Nginx configuration
The following fragment shows a basic section of a typical Nginx configuration file.
server {
listen 80 default_server;
server_name example.com;
location / {
proxy_set_header X-Forwarded-Host $host:$server_port;
proxy_set_header X-Forwarded-Server $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://localhost:8080;
}
}
Let's take a closer look at the location section. We use this section to add the values we need downstream - in our backend application - as request headers.
X-Forwarded-Host
Specifies the hostname that was requested by the client. This typically includes the requested port.
X-Forwarded-Server
Specifies the hostname of the reverse proxy server.
X-Forwarded-For
Specifies the IP address of the client requesting the page. The variable $proxy_add_x_forwarded_for stores the remote IP address and we simply copy the actual value to the X-Forwarded-For header.
List of available variables can be found here.
2. Extract the header in the Spring Boot web application
In case you want to log the remote IP address for all requests to the backend, I recommend to the create a HandlerIntercepter that will automatically extract the information from all requests and executes the logic to store the data.
HandlerInterceptor implementation
The class below shows a simple HandlerInterceptor that extracts and logs the remote IP address in conjunction with the requested URI. As we specified above that Nginx should provide the remote IP address in the header named "X-Forwarded-For", we can simply extract the value with a statement like request.getHeader("X-Forwarded-For").
The full example of our HandlerInterceptor implementation is below:
package com.progressive.code.starter.interceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
/* Name "RequestLogger" to be able to use @Autowired in the configuration */
@Component("RequestLogger")
public class RequestLogger implements HandlerInterceptor {
private static final Logger LOGGER = LoggerFactory.getLogger(
RequestLogger.class);
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
Object handler)
throws Exception {
/* Logs the "wrong" ip - the ip of the reverse proxy, in our example 127.0.0.1 */
LOGGER.info("Page {} was requested by IP {}", request.getRequestURI(),
request.getRemoteAddr());
/* Logs the "real" remote ip address, as set by Nginx */
LOGGER.info("Page {} was requested by IP {}", request.getRequestURI(),
request.getHeader("X-Forwarded-For"));
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response,
Object handler,ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
Object handler, Exception ex) throws Exception {
}
}
Configure the Interceptor
The last step is to configure the Spring Boot Web MVC component to use our new RequestLogger. I prefer to name each HandlerInterceptor I create and use @Autowired in conjunction with @Qualifier to inject the interceptors to use. After the interceptor has been injected we only need to register it using a statement like registry.addInterceptor(this.requestLogger);
package com.progressive.code.starter.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
@Configuration
public class WebMvcConfig extends WebMvcConfigurerAdapter {
@Autowired
@Qualifier("RequestLogger")
HandlerInterceptor requestLogger;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(this.requestLogger);
}
}
3. Test the implementation
When sending a request the application now creates the following example log entries:
Page / was requested by IP 127.0.0.1
Page / was requested by IP 52.128.128.10
As mentioned above the first log entry represents the reverse proxy IP and the second one the remote IP.
Tags
AOP Apache Kafka Bootstrap Go Java Linux MongoDB Nginx Security Spring Spring Boot Spring Security SSL ThymeleafSearch
Archive
- 1 December 2023
- 1 November 2023
- 1 May 2019
- 2 April 2019
- 1 May 2018
- 1 April 2018
- 1 March 2018
- 2 February 2018
- 1 January 2018
- 5 December 2017
- 7 November 2017
- 2 October 2017