diff --git a/gateway/pom.xml b/gateway/pom.xml index e58681e..26aa8a2 100644 --- a/gateway/pom.xml +++ b/gateway/pom.xml @@ -5,7 +5,7 @@ org.springframework.boot spring-boot-starter-parent - 2.1.8.RELEASE + 2.2.10.RELEASE me.aboullaite @@ -16,7 +16,7 @@ 1.8 - Greenwich.RELEASE + Hoxton.SR8 diff --git a/gateway/src/main/java/me/aboullaite/gateway/NettyReactiveWebServerConfig.java b/gateway/src/main/java/me/aboullaite/gateway/NettyReactiveWebServerConfig.java new file mode 100644 index 0000000..c0750c7 --- /dev/null +++ b/gateway/src/main/java/me/aboullaite/gateway/NettyReactiveWebServerConfig.java @@ -0,0 +1,67 @@ +package me.aboullaite.gateway; + +import io.netty.handler.codec.http.HttpRequest; +import org.springframework.boot.web.embedded.netty.NettyReactiveWebServerFactory; +import org.springframework.boot.web.server.WebServerFactoryCustomizer; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.server.reactive.ServerHttpRequest; +import reactor.netty.http.server.ConnectionInfo; +import reactor.netty.tcp.InetSocketAddressUtil; + +import java.net.InetSocketAddress; +import java.util.function.BiFunction; + +/** + * This configuration customizes the behavior of remote address resolver. As a result, + * {@link ServerHttpRequest#getRemoteAddress} will return IP address that is extracted from X-Forwarded-For header + * by our {@link CustomizedHttpForwardedHeaderHandler}. + */ +@Configuration +public class NettyReactiveWebServerConfig implements WebServerFactoryCustomizer { + + @Override + public void customize(NettyReactiveWebServerFactory factory) { + factory.addServerCustomizers(httpServer -> httpServer.forwarded(new CustomizedHttpForwardedHeaderHandler())); + } + + static class CustomizedHttpForwardedHeaderHandler + implements BiFunction { + + static final String X_FORWARDED_IP_HEADER = "X-Forwarded-For"; + + /** + * Attempt to extract the last IP address from X-Forwarded-For header. If the header is present, create a + * ConnectionInfo instance with the extracted IP address and then return it. Otherwise, return the + * connectionInfo passed in. On the contrary, DefaultHttpForwardedHeaderHandler extracts the first IP + * address from the header when the header is present. + * + * @param connectionInfo + * @param request + * @return + */ + @Override + public ConnectionInfo apply(ConnectionInfo connectionInfo, HttpRequest request) { + String ipHeader = request.headers().get(X_FORWARDED_IP_HEADER); + + if (ipHeader != null) { + String[] ips = ipHeader.split(","); + String ip = ips[ips.length - 1].trim(); + + // If the IP address is internal, we will skip it and extract the previous one. + if (ips.length >= 2 && isInternal(ip)) { + ip = ips[ips.length - 2].trim(); + } + + InetSocketAddress remoteAddress = InetSocketAddressUtil.parseAddress(ip, + connectionInfo.getRemoteAddress().getPort()); + connectionInfo = connectionInfo.withRemoteAddress(remoteAddress); + } + + return connectionInfo; + } + + private boolean isInternal(String ip) { + return ip.startsWith("10."); + } + } +} diff --git a/gateway/src/main/java/me/aboullaite/gateway/controller/RemoteAddressController.java b/gateway/src/main/java/me/aboullaite/gateway/controller/RemoteAddressController.java new file mode 100644 index 0000000..45d34a2 --- /dev/null +++ b/gateway/src/main/java/me/aboullaite/gateway/controller/RemoteAddressController.java @@ -0,0 +1,27 @@ +package me.aboullaite.gateway.controller; + +import me.aboullaite.gateway.NettyReactiveWebServerConfig; +import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * A demo controller that shows the customized behavior of {@link ServerHttpRequest#getRemoteAddress}. The + * customization class is CustomizedHttpForwardedHeaderHandler in {@link NettyReactiveWebServerConfig}. + */ +@RestController +public class RemoteAddressController { + + /** + * @param request + * @return Value of X-Forwarded-For header, as well as result of {@link ServerHttpRequest#getRemoteAddress} + */ + @GetMapping("/remote_address") + public String getRemoteAddress(ServerHttpRequest request) { + return String.format("X-Forwarded-For: %s\n" + + "getRemoteAddress() returns %s", + request.getHeaders().get("X-Forwarded-For"), + request.getRemoteAddress() + ); + } +}