客户端请求到服务端时,可能会被 HTTP 代理、负载均衡等转发服务进行处理,再转发,如果要获取最原始的客户端真实IP,需要根据常用的协议,进行获取。
参考:HTTP 请求头中的 X-Forwarded-For https://www.jianshu.com/p/15f3498a7fad
SpringBoot获取请求的IP地址 https://www.jianshu.com/p/3871a2c47c09
如何获取
1.spring cloud gateway中获取客户端真实ip的方式和普通java web应用中获取的方式类似,即从request中先从代理转发的相关请求头中获取原始客户端ip,如果获取不到,则取当前与服务器通信的客户端对应的ip
2.示例代码
(1)创建获取IP的工具类
/**
* IP工具类
* @date 20-5-15 10:03
*/
public class IpUtils {
private static final LogTool LOGGER = LogTool.getLogger(IpUtils.class);
private static final String UNKNOWN = "unknown";
private static final String LOCALHOST = "127.0.0.1";
private static final String SEPARATOR = ",";
private static final String HEADER_X_FORWARDED_FOR = "x-forwarded-for";
private static final String HEADER_PROXY_CLIENT_IP = "Proxy-Client-IP";
private static final String HEADER_WL_PROXY_CLIENT_IP = "WL-Proxy-Client-IP";
/**
* 获取真实客户端IP
* @param serverHttpRequest
* @return
*/
public static String getRealIpAddress(ServerHttpRequest serverHttpRequest) {
String ipAddress;
try {
// 1.根据常见的代理服务器转发的请求ip存放协议,从请求头获取原始请求ip。值类似于203.98.182.163, 203.98.182.163
ipAddress = serverHttpRequest.getHeaders().getFirst(HEADER_X_FORWARDED_FOR);
if (StringUtil.isTrimEmpty(ipAddress) || UNKNOWN.equalsIgnoreCase(ipAddress)) {
ipAddress = serverHttpRequest.getHeaders().getFirst(HEADER_PROXY_CLIENT_IP);
}
if (StringUtil.isTrimEmpty(ipAddress) || UNKNOWN.equalsIgnoreCase(ipAddress)) {
ipAddress = serverHttpRequest.getHeaders().getFirst(HEADER_WL_PROXY_CLIENT_IP);
}
// 2.如果没有转发的ip,则取当前通信的请求端的ip
if (StringUtil.isTrimEmpty(ipAddress) || UNKNOWN.equalsIgnoreCase(ipAddress)) {
InetSocketAddress inetSocketAddress = serverHttpRequest.getRemoteAddress();
if(inetSocketAddress != null) {
ipAddress = inetSocketAddress.getAddress().getHostAddress();
}
// 如果是127.0.0.1,则取本地真实ip
if (LOCALHOST.equals(ipAddress)) {
InetAddress localAddress = StringUtil.getLocalHostLANAddress();
if(localAddress.getHostAddress() != null) {
ipAddress = localAddress.getHostAddress();
}
}
}
// 对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割
// "***.***.***.***"
if (ipAddress != null) {
ipAddress = ipAddress.split(SEPARATOR)[0].trim();
}
} catch (Exception e) {
LOGGER.error("解析请求IP失败", e);
ipAddress = "";
}
return ipAddress == null ? "" : ipAddress;
}
}
(2)使用GlobalFilter过滤器拦截
@Component
public class AccessInFilter implements GlobalFilter, Ordered {
private static LogTool logger = LogTool.getLogger(AccessInFilter.class);
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String ip = IpUtils.getRealIpAddress(exchange.getRequest());
// 该步骤可选。可以传递给下游服务器,用于业务处理
ServerHttpRequest request = exchange.getRequest().mutate()
.header(GatewayConstant.REQUEST_HEADER_KEY_CLIENT_REAL_IP, new String[]{ip})
.build();
logger.info("访问入口过滤器AccessInFilter.";
return chain.filter(exchange.mutate().request(request).build());
}
@Override
public int getOrder() {
return -3;
}
}
注意:
1.上面的方法其实也未必一定能获取到真实IP,因为代理服务可能没有按常用的协议进行处理,比如,没有采用x-forwarded-for这些请求头对原始ip进行透传,或者删除了这些请求头。那原始的ip信息将会丢失。
2.没有采用spring cloud gateway封装的方式
RemoteAddressResolver resolver = XForwardedRemoteAddressResolver.trustAll();
InetAddress inetAddress = resolver.resolve(exchange).getAddress();
String ip = inetAddress == null ? "" : resolver.resolve(exchange).getAddress().getHostAddress();
因为该方式代码有缺陷,查看源码逻辑:
org.springframework.cloud.gateway.support.ipresolver.XForwardedRemoteAddressResolver#extractXForwardedValues
private List<String> extractXForwardedValues(ServerWebExchange exchange) {
List<String> xForwardedValues = exchange.getRequest().getHeaders()
.get(X_FORWARDED_FOR);
if (xForwardedValues == null || xForwardedValues.isEmpty()) {
return Collections.emptyList();
}
if (xForwardedValues.size() > 1) {
log.warn("Multiple X-Forwarded-For headers found, discarding all");
return Collections.emptyList();
}
// 此处有问题,写死了是逗号加空格进行分割,但是有时只有逗号,不存在空格,这种情况,会导致解析失败,返回的空的ip
List<String> values = Arrays.asList(xForwardedValues.get(0).split(", "));
if (values.size() == 1 && !StringUtils.hasText(values.get(0))) {
return Collections.emptyList();
}
return values;
}
网友评论