How to get remote address from request of Netty HTTP Server
获取 remoteAddress 的逻辑
通过HTTP请求获取 remote address 时,最终是调用 ConnectionInfo 的 getRemoteAddress() 方法。
reactor.netty.http.server.HttpServerOperations#remoteAddress
@Override
public InetSocketAddress remoteAddress() {
return this.connectionInfo.getRemoteAddress();
}
ConnectionInfo 实例的创建过程如下:
从 “Forwarded” 或 "X-Forwarded-" HTTP请求头中获取连接信息,如果不存在则直接从当前连接获取连接信息。*
请注意:如果 Nginx 和 Netty HTTP Server 位于同一服务器内,且未启动 use-forward-headers,则获取的 remoteAddress 为 127.0.0.1 。
reactor.netty.http.server.ConnectionInfo#from
static ConnectionInfo from(Channel channel, boolean headers, HttpRequest request, boolean secured) {
if (headers) {
return ConnectionInfo.newForwardedConnectionInfo(request, channel, secured);
}
else {
return ConnectionInfo.newConnectionInfo(channel, secured);
}
}
static ConnectionInfo newForwardedConnectionInfo(HttpRequest request, Channel channel, boolean secured) {
if (request.headers().contains(FORWARDED_HEADER)) {
return parseForwardedInfo(request, (SocketChannel)channel, secured);
}
else {
return parseXForwardedInfo(request, (SocketChannel)channel, secured);
}
}
static ConnectionInfo parseXForwardedInfo(HttpRequest request, SocketChannel channel, boolean secured) {
InetSocketAddress hostAddress = channel.localAddress();
InetSocketAddress remoteAddress = getRemoteAddress(channel);
String scheme = secured ? "https" : "http";
if (request.headers().contains(XFORWARDED_IP_HEADER)) {
String remoteIpValue = request.headers().get(XFORWARDED_IP_HEADER).split(",")[0];
remoteAddress = parseAddress(remoteIpValue, remoteAddress.getPort());
}
if(request.headers().contains(XFORWARDED_HOST_HEADER)) {
if(request.headers().contains(XFORWARDED_PORT_HEADER)) {
hostAddress = InetSocketAddressUtil.createUnresolved(
request.headers().get(XFORWARDED_HOST_HEADER).split(",")[0].trim(),
Integer.parseInt(request.headers().get(XFORWARDED_PORT_HEADER).split(",")[0].trim()));
}
else {
hostAddress = InetSocketAddressUtil.createUnresolved(
request.headers().get(XFORWARDED_HOST_HEADER).split(",")[0].trim(),
channel.localAddress().getPort());
}
}
if (request.headers().contains(XFORWARDED_PROTO_HEADER)) {
scheme = request.headers().get(XFORWARDED_PROTO_HEADER).trim();
}
return new ConnectionInfo(hostAddress, remoteAddress, scheme);
}
通过配置文件启用 use-forward-headers
要想通过 request header 获取连接信息,需要配置 use-forward-headers = true。(默认为 false)
server:
port: 9090
use-forward-headers: true
通过 NettyWebServerFactoryCustomizer 配置
也可以通过NettyWebServerFactoryCustomizer启动,示例代码如下:
@Configuration
public class NettyWebServerConfiguration {
@Bean
public NettyWebServerFactoryCustomizer nettyServerWiretapCustomizer(
Environment environment, ServerProperties serverProperties) {
return new NettyWebServerFactoryCustomizer(environment, serverProperties) {
@Override
public void customize(NettyReactiveWebServerFactory factory) {
factory.addServerCustomizers(httpServer -> httpServer.forwarded(true));
super.customize(factory);
}
};
}
}
set X-Forwarded-For header from nginx
1.通过“proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for” 把从真实客户端IP和反向代理IP通过逗号分隔,添加到请求头中;
2.可以在第一个反向代理上配置“proxy_set_header X-Real-IP $remote_addr” 获取真实客户端IP;
3.配合 realip 模块从 X-Forwarded-For 也可以获取到真实客户端IP。在整个反向代理链条的第一个反向代理可以不配置“proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for”,否则客户端可以伪造 X-Forwarded-For 从而伪造客户端真实 IP,如果服务端使用 X-Forwarded-For 第一个IP作为真实客户端 IP,则就出问题了。如果通过配置 X-Real-IP 请求头或者配合 realip 模块也不会出现该问题。如果自己解析 X-Forwarded-For 的话,记得使用 realip 算法解析,而不是取第一个。
当我们进行限流时一定注意限制的是真实客户端IP,而不是反向代理IP,我遇到过很多同事在这块出问题的。
location / {
proxy_next_upstream http_500 http_502 http_503 http_504 error timeout invalid_header;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://tomcat_microservice;
expires 0;
}