1. Nginx configuration

The following fragment shows a basic section of a typical Nginx configuration file.

nginx.conf
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.

Header Description
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.

HandlerInterceptor.java
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 {
    }
}

To demonstrate the difference I have added request.getRemoteAddr() to show that behind a reverse proxy the value usually points to the reverse proxy server.

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);

WebMvcConfigurerAdapter.java
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:

log.txt
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.