Imagine that you are looking to restrict access to a highly sensitive Spring Boot endpoint. You decide that only requests coming from a specific internal management IP address – say, 192.168.1.1 – should ever be allowed to see it.
To enforce this, you write a standard servlet filter. It looks perfectly clean, logical, and secure:
package com.example.demo;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.filter.OncePerRequestFilter;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.io.IOException;
@Component
class IpFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain)
throws ServletException, IOException {
String remoteAddress = request.getRemoteAddr();
if ("192.168.1.1".equals(remoteAddress)) {
filterChain.doFilter(request, response); // Let them through
} else {
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
response.getWriter().write("Forbidden: Access restricted to 192.168.1.1");
}
}
}
@RestController
class HelloController {
@GetMapping("/")
public String index() {
return "Hello 192.168.1.1! You have successfully bypassed the filter.";
}
}
The logic is simple. If request.getRemoteAddr() matches our trusted IP, they pass. Otherwise, they get a 403 Forbidden.
Does it actually work? It depends entirely on your configuration.
The Silent Danger: forward-headers-strategy
In a modern cloud infrastructure, your Spring Boot application rarely sits directly on the public internet. It usually hides behind a reverse proxy, a load balancer, or an API gateway like NGINX, AWS ALB, or Cloudflare. Because the application only “sees” the incoming connection from the proxy itself, request.getRemoteAddr() would normally return the proxy’s IP address rather than the client’s. To solve this dilemma, proxies attach HTTP headers like X-Forwarded-For to pass the original identity along.
How Spring Boot handles these headers is dictated by the server.forward-headers-strategy property. This configuration knob influences whether your filter works as intended or not.
The Safe Route (none)
server:
forward-headers-strategy: none
When the strategy is set to none, Spring Boot completely ignores incoming X-Forwarded-* headers. The underlying server evaluates the request using only the actual network socket information.
As a result, request.getRemoteAddr() strictly returns the physical IP address of the network connection. If an external attacker tries to spoof their identity by sending a custom header like X-Forwarded-For: 192.168.1.1, Spring Boot simply discards it, and your security filter works exactly as intended.
The Dangerous Route (native / framework)
server:
forward-headers-strategy: native
When you set the strategy to native or framework, you are telling Spring Boot to trust the upstream headers explicitly and extract the client’s real IP address from them. Under the hood, the web server extracts the value of X-Forwarded-For and overrides what request.getRemoteAddr() returns.
This introduces a severe vulnerability if your edge proxy or load balancer is not explicitly configured to strip or overwrite incoming X-Forwarded-For headers from the public internet. An attacker can easily send a malicious request directly to your setup containing a spoofed header:
GET / HTTP/1.1
Host: your-app.com
X-Forwarded-For: 192.168.1.1
Spring Boot processes this header, updates the internal tracking of the remote address, and your IpFilter happily waves the attacker through, under the false impression that the connection originated locally.
Conclusion and Strategic Takeaways
Securing an application requires a deep understanding of how code interacts with environmental configuration. Relying purely on application-level logic to police network identity can backfire when configuration flags change the meaning of your API calls.
To be clear, Spring Boot is not doing anything wrong by supporting X-Forwarded-For headers. The issue is simply that these headers cannot be trusted unless they are set by trusted proxy servers. In particular, if the first proxy server in the chain that you trust does not strip and overwrite these headers from client messages, then filtering or IP-based controls can be bypassed easily.