For a security check I need access to the user's remote IP address in my resource service. This resource service is a simple recent Spring Boot app, that registers itself with my Eureka server:
@SpringBootApplication
@EnableEurekaClient
public class ServletInitializer extends SpringBootServletInitializer {
public static void main(final String[] args) {
SpringApplication.run(ServletInitializer.class, args);
}
}
All services registered with my Eureka server are dynamically routed through my Zuul routing proxy server based on Angel.SR3 starter-zuul
and starter-eureka
:
@SpringBootApplication
@EnableZuulProxy
@EnableEurekaClient
public class RoutingProxyServer {
public static void main(final String[] args) {
SpringApplication.run(RoutingProxyServer.class, args);
}
}
The Zuul routing proxy server also configures an AJP connector for the next step:
@Configuration
@ConditionalOnProperty("ajp.port")
public class TomcatAjpConfig extends TomcatWebSocketContainerCustomizer {
@Value("${ajp.port}")
private int port;
@Override
public void doCustomize(final TomcatEmbeddedServletContainerFactory tomcat) {
super.doCustomize(tomcat);
// Listen for AJP requests
Connector ajp = new Connector("AJP/1.3");
ajp.setPort(port);
tomcat.addAdditionalTomcatConnectors(ajp);
}
}
All requests to the dynamic routing zuul proxy are proxied themselves through Apache to provide HTTPS on the standard 443 port:
# Preserve Host when proxying so jar apps return working URLs in JSON responses
RequestHeader set X-Forwarded-Proto "https"
ProxyPreserveHost On
# Redirect remaining traffic to routing proxy server
ProxyPass / ajp://192.168.x.x:8009/
# Also update Location, Content-Location and URI headers on HTTP redirect responses
ProxyPassReverse / ajp://192.168.x.x:8009/
With all this in place the resource service is made available, but unfortunately the remoteAddress that I get from Spring Security is the address of the Zuul proxy/Apache server, not the remote client IP address.
In the past I had used a org.springframework.security.authentication.AuthenticationDetailsSource
that preferred the X-Forwarded-For
header value over the normal remoteAddress
to get the correct IP address, but I can not work out how to pass the proper remote IP address to my resource service when passing through two proxies (Apache + Zuul).
Can anyone help me access the correct remote IP address behind these two proxies, or suggest an alternative approach to get this to work?
Zuul is an edge service that proxies requests to multiple backing services. It provides a unified “front door” to your system, which allows a browser, mobile app, or other user interface to consume services from multiple hosts without managing cross-origin resource sharing (CORS) and authentication for each one.
Zuul is built on servlet 2.5 (works with 3. x), using blocking APIs. It doesn't support any long lived connections, like websockets. Gateway is built on Spring Framework 5, Project Reactor and Spring Boot 2 using non-blocking APIs.
It handles all the requests and performs the dynamic routing of microservice applications. It is also known as Edge Server. Zuul is built to enable dynamic routing, monitoring, resiliency, and security. It can also route the requests to multiple Amazon Auto Scaling Groups.
Turns out the X-Forwarded-For header was taken from the original request feeding into Zuul to populate HttpServletRequest#getRemoteAddr()
. This would then have to be passed on to the proxied backend services through RequestContext#getZuulRequestHeaders().put("X-Forwarded-For", remoteAddr)
. The following ZuulFilter
accomplishes this, even if it isn't appending it's own value to the X-Forwarded-For
filter just yet.
@Component
@Slf4j
public class XForwardedForFilter extends ZuulFilter {
private static final String X_FORWARDED_FOR = "X-Forwarded-For";
@Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
// Rely on HttpServletRequest to retrieve the correct remote address from upstream X-Forwarded-For header
HttpServletRequest request = ctx.getRequest();
String remoteAddr = request.getRemoteAddr();
// Pass remote address downstream by setting X-Forwarded for header again on Zuul request
log.debug("Settings X-Forwarded-For to: {}", remoteAddr);
ctx.getZuulRequestHeaders().put(X_FORWARDED_FOR, remoteAddr);
return null;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public String filterType() {
return "pre";
}
@Override
public int filterOrder() {
return 10000;
}
}
One might want to clear the header value in Apache before proxying to Zuul to prevent accepting just any value provided by the user: RequestHeader unset X-Forwarded-For
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With