引言:为什么你的ip获取方式可能是错的?
在日常开发中,获取客户端ip看似简单,实则暗藏玄机。很多开发者直接使用request.getremoteaddr(),结果在生产环境中发现获取到的都是负载均衡器的ip,而非真实用户ip。更糟糕的是,有些方案存在安全漏洞,可能被恶意用户伪造ip地址。
今天,我将彻底揭秘spring boot中获取真实客户端ip的正确姿势,让你避开所有坑!
一、理解ip传递的底层原理
1.1 为什么需要特殊处理
在现代web架构中,请求往往要经过多个中间件:
用户 → cdn → 负载均衡器 → 网关 → 应用服务器
每个环节都会修改请求信息,导致简单的getremoteaddr()失效。
1.2 关键http头字段解析
| 头字段 | 含义 | 示例 | 可信度 |
|---|---|---|---|
| x-forwarded-for | 代理链ip序列 | 1.2.3.4, 5.6.7.8 | ⭐⭐⭐⭐ |
| x-real-ip | 最后一个代理ip | 1.2.3.4 | ⭐⭐⭐ |
| proxy-client-ip | apache代理ip | 1.2.3.4 | ⭐⭐ |
| wl-proxy-client-ip | weblogic代理ip | 1.2.3.4 | ⭐⭐ |
1.3 x-forwarded-for的深度解析
这才是重点! x-forwarded-for是获取真实ip的关键,但很多人用错了!
x-forwarded-for: 客户端真实ip, 代理服务器1ip, 代理服务器2ip, ...
重要规则:
- 最左边的ip是原始客户端ip
- 后续ip是经过的代理服务器ip
- 多个ip用逗号分隔
实际场景示例:
// 场景1:直接访问(无代理) x-forwarded-for: null // 场景2:经过cdn x-forwarded-for: 123.45.67.89 // 场景3:cdn + nginx负载均衡 x-forwarded-for: 123.45.67.89, 10.0.1.100 // 场景4:复杂代理链 x-forwarded-for: 123.45.67.89, 203.0.113.195, 198.51.100.10
二、终极解决方案:安全可靠的ip工具类
下面这个工具类经过生产环境千锤百炼,直接复制使用即可!
import javax.servlet.http.httpservletrequest;
import java.util.arrays;
import java.util.hashset;
import java.util.set;
/**
* ip工具类 - 获取真实客户端ip地址
* 支持多级代理、防止ip伪造、安全可靠
*/
public class iputils {
private static final string unknown = "unknown";
private static final string localhost_ip = "127.0.0.1";
private static final string localhost_ipv6 = "0:0:0:0:0:0:0:1";
private static final string separator = ",";
// 内网ip段(用于识别代理服务器)
private static final set<string> internal_ip_segments = new hashset<>(arrays.aslist(
"10.", "192.168.", "172.16.", "172.17.", "172.18.", "172.19.",
"172.20.", "172.21.", "172.22.", "172.23.", "172.24.", "172.25.",
"172.26.", "172.27.", "172.28.", "172.29.", "172.30.", "172.31."
));
/**
* 获取真实客户端ip(推荐使用)
* 安全可靠,防止伪造,支持多级代理
*/
public static string getclientrealip(httpservletrequest request) {
// 1. 优先检查x-forwarded-for(处理多级代理)
string ip = parsexforwardedfor(request.getheader("x-forwarded-for"));
if (isvalidpublicip(ip)) {
return ip;
}
// 2. 检查其他代理头
ip = getipfromheaders(request);
if (isvalidpublicip(ip)) {
return ip;
}
// 3. 最后使用remoteaddr
ip = request.getremoteaddr();
return localhost_ipv6.equals(ip) ? localhost_ip : ip;
}
/**
* 解析x-forwarded-for头(核心逻辑)
*/
private static string parsexforwardedfor(string xffheader) {
if (xffheader == null || xffheader.trim().isempty()) {
return null;
}
string[] ips = xffheader.split(separator);
// 从右向左查找第一个公网ip(更安全)
for (int i = ips.length - 1; i >= 0; i--) {
string ip = ips[i].trim();
if (isvalidip(ip) && !isinternalip(ip)) {
return ip;
}
}
// 如果没有公网ip,返回第一个有效ip
for (string ip : ips) {
string trimmedip = ip.trim();
if (isvalidip(trimmedip)) {
return trimmedip;
}
}
return null;
}
/**
* 从其他头字段获取ip
*/
private static string getipfromheaders(httpservletrequest request) {
string[] headers = {
"x-real-ip", "proxy-client-ip", "wl-proxy-client-ip",
"http_client_ip", "http_x_forwarded_for"
};
for (string header : headers) {
string ip = request.getheader(header);
if (isvalidip(ip)) {
return ip;
}
}
return null;
}
/**
* 验证ip是否有效
*/
private static boolean isvalidip(string ip) {
return ip != null &&
!ip.isempty() &&
!unknown.equalsignorecase(ip) &&
isvalidipaddress(ip);
}
/**
* 验证是否为公网ip
*/
private static boolean isvalidpublicip(string ip) {
return isvalidip(ip) && !isinternalip(ip) && !islocalhost(ip);
}
/**
* 检查是否为内网ip
*/
private static boolean isinternalip(string ip) {
if (ip == null) return false;
return internal_ip_segments.stream().anymatch(ip::startswith);
}
/**
* 检查是否为本地地址
*/
private static boolean islocalhost(string ip) {
return localhost_ip.equals(ip) || localhost_ipv6.equals(ip);
}
/**
* 验证ip地址格式
*/
public static boolean isvalidipaddress(string ip) {
if (ip == null || ip.isempty()) return false;
// ipv4验证
string ipv4pattern = "^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$";
if (ip.matches(ipv4pattern)) return true;
// ipv6简化验证
if (ip.contains(":")) return true;
return false;
}
}
三、spring boot配置:让服务器认识代理
3.1 tomcat代理配置
@configuration
public class tomcatproxyconfig {
/**
* 配置tomcat识别代理头
*/
@bean
public webserverfactorycustomizer<tomcatservletwebserverfactory> tomcatproxycustomizer() {
return factory -> factory.addconnectorcustomizers(connector -> {
connector.setproperty("relaxedquerychars", "|{}[]");
connector.setproperty("relaxedpathchars", "|{}[]");
connector.setproperty("remoteipheader", "x-forwarded-for");
connector.setproperty("protocolheader", "x-forwarded-proto");
// 信任的内网代理(根据实际情况调整)
connector.setproperty("internalproxies",
"192\\.168\\.\\d{1,3}\\.\\d{1,3}|10\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}|172\\.(1[6-9]|2[0-9]|3[0-1])\\.\\d{1,3}\\.\\d{1,3}");
});
}
}
3.2 应用配置文件
# application.yml
server:
tomcat:
remoteip:
remote-ip-header: x-forwarded-for
protocol-header: x-forwarded-proto
internal-proxies: |
192\.168\.\d{1,3}\.\d{1,3}|10\.\d{1,3}\.\d{1,3}\.\d{1,3}|
172\.(1[6-9]|2[0-9]|3[0-1])\.\d{1,3}\.\d{1,3}
spring:
mvc:
log-request-details: true
四、高级功能:ip拦截与安全防护
4.1 ip拦截器(自动记录)
@component
public class iplogginginterceptor implements handlerinterceptor {
private static final logger logger = loggerfactory.getlogger(iplogginginterceptor.class);
@override
public boolean prehandle(httpservletrequest request,
httpservletresponse response,
object handler) {
string clientip = iputils.getclientrealip(request);
request.setattribute("clientrealip", clientip);
// 记录访问日志
logger.info("客户端访问: ip={}, uri={}, user-agent={}",
clientip,
request.getrequesturi(),
request.getheader("user-agent"));
return true;
}
}
@configuration
public class webmvcconfig implements webmvcconfigurer {
@autowired
private iplogginginterceptor iplogginginterceptor;
@override
public void addinterceptors(interceptorregistry registry) {
registry.addinterceptor(iplogginginterceptor)
.addpathpatterns("/**")
.excludepathpatterns("/health", "/metrics");
}
}
4.2 ip安全过滤器(防刷/黑名单)
@component
@order(1)
public class ipsecurityfilter implements filter {
// ip黑名单(可从数据库或配置中心加载)
private final set<string> blacklistedips = concurrenthashmap.newkeyset();
// ip访问频率限制(简单的内存实现,生产环境建议用redis)
private final map<string, ratelimitinfo> ratelimitmap = new concurrenthashmap<>();
@override
public void dofilter(servletrequest request, servletresponse response,
filterchain chain) throws ioexception, servletexception {
httpservletrequest httprequest = (httpservletrequest) request;
string clientip = iputils.getclientrealip(httprequest);
// 1. 黑名单检查
if (blacklistedips.contains(clientip)) {
logsecurityevent("ip黑名单拦截", clientip, httprequest);
senderrorresponse(response, 403, "您的ip已被禁止访问");
return;
}
// 2. 频率限制检查
if (isratelimited(clientip)) {
logsecurityevent("频率限制拦截", clientip, httprequest);
senderrorresponse(response, 429, "访问过于频繁,请稍后再试");
return;
}
// 3. 可疑行为检测
if (issuspiciousrequest(clientip, httprequest)) {
logsecurityevent("可疑请求拦截", clientip, httprequest);
blacklistedips.add(clientip); // 自动加入黑名单
senderrorresponse(response, 403, "检测到异常访问行为");
return;
}
chain.dofilter(request, response);
}
private boolean isratelimited(string ip) {
ratelimitinfo info = ratelimitmap.computeifabsent(ip, k -> new ratelimitinfo());
long currenttime = system.currenttimemillis();
// 限制规则:每分钟最多60次请求
if (currenttime - info.getwindowstart() > 60000) {
info.reset(60, currenttime);
}
return !info.tryacquire();
}
private boolean issuspiciousrequest(string ip, httpservletrequest request) {
// 检测异常user-agent
string useragent = request.getheader("user-agent");
if (useragent == null || useragent.trim().isempty()) {
return true;
}
// 检测常见攻击特征
string uri = request.getrequesturi().tolowercase();
if (uri.contains("admin") || uri.contains("phpmyadmin") ||
uri.contains("wp-admin") || uri.contains("shell")) {
return true;
}
return false;
}
private void senderrorresponse(servletresponse response, int status, string message)
throws ioexception {
httpservletresponse httpresponse = (httpservletresponse) response;
httpresponse.setstatus(status);
httpresponse.setcontenttype("application/json;charset=utf-8");
httpresponse.getwriter().write("{\"code\": " + status + ", \"message\": \"" + message + "\"}");
}
private void logsecurityevent(string event, string ip, httpservletrequest request) {
logger.warn("安全事件: {} - ip: {}, uri: {}, user-agent: {}",
event, ip, request.getrequesturi(), request.getheader("user-agent"));
}
// 频率限制内部类
private static class ratelimitinfo {
private int tokens;
private long windowstart;
private final int maxtokens = 60;
ratelimitinfo() {
reset(maxtokens, system.currenttimemillis());
}
void reset(int tokens, long windowstart) {
this.tokens = tokens;
this.windowstart = windowstart;
}
boolean tryacquire() {
if (tokens > 0) {
tokens--;
return true;
}
return false;
}
long getwindowstart() {
return windowstart;
}
}
}
五、实战测试:验证你的ip获取是否正确
5.1 调试控制器
@restcontroller
@requestmapping("/debug")
public class ipdebugcontroller {
@getmapping("/ip")
public map<string, object> debugip(httpservletrequest request) {
map<string, object> result = new linkedhashmap<>();
// 真实ip
result.put("真实客户端ip", iputils.getclientrealip(request));
// 各种头字段对比
result.put("remoteaddr", request.getremoteaddr());
result.put("x-forwarded-for", request.getheader("x-forwarded-for"));
result.put("x-real-ip", request.getheader("x-real-ip"));
result.put("proxy-client-ip", request.getheader("proxy-client-ip"));
result.put("wl-proxy-client-ip", request.getheader("wl-proxy-client-ip"));
// 请求详细信息
result.put("请求方法", request.getmethod());
result.put("请求uri", request.getrequesturi());
result.put("user-agent", request.getheader("user-agent"));
return result;
}
@getmapping("/ip-headers")
public map<string, string> getallipheaders(httpservletrequest request) {
map<string, string> headers = new linkedhashmap<>();
string[] ipheaders = {
"x-forwarded-for", "x-real-ip", "proxy-client-ip",
"wl-proxy-client-ip", "http_x_forwarded_for", "http_x_forwarded",
"http_x_cluster_client_ip", "http_client_ip", "http_forwarded_for",
"http_forwarded", "http_via", "remote_addr"
};
for (string header : ipheaders) {
string value = request.getheader(header);
if (value != null && !value.trim().isempty()) {
headers.put(header, value);
}
}
return headers;
}
}
5.2 测试用例
@springboottest
class iputilstest {
@test
void testgetclientrealip() {
// 模拟httpservletrequest
mockhttpservletrequest request = new mockhttpservletrequest();
// 测试场景1:直接访问
request.setremoteaddr("123.45.67.89");
assertequals("123.45.67.89", iputils.getclientrealip(request));
// 测试场景2:单层代理
request.addheader("x-forwarded-for", "123.45.67.89");
request.setremoteaddr("10.0.0.1");
assertequals("123.45.67.89", iputils.getclientrealip(request));
// 测试场景3:多层代理
request.addheader("x-forwarded-for", "123.45.67.89, 10.0.1.100, 10.0.1.101");
assertequals("123.45.67.89", iputils.getclientrealip(request));
// 测试场景4:ipv6
request.addheader("x-forwarded-for", "2001:db8::1");
assertequals("2001:db8::1", iputils.getclientrealip(request));
}
}
六、生产环境最佳实践
6.1 配置管理
- 将信任的代理ip列表配置在配置中心,支持动态更新
- 为不同环境(开发、测试、生产)设置不同的代理配置
6.2 监控告警
@component
public class ipmonitor {
@eventlistener
public void handleblacklistevent(blacklistevent event) {
// 发送告警通知
alertservice.sendalert("ip黑名单新增: " + event.getip());
}
@scheduled(fixedrate = 300000) // 5分钟执行一次
public void cleanupratelimit() {
// 定期清理过期的频率限制记录
}
}
6.3 性能优化
- 对于高并发场景,使用redis实现分布式频率限制
- 对ip查询结果进行适当缓存(注意缓存时间不宜过长)
七、常见问题排查
q1: 为什么获取到的还是127.0.0.1?
a: 检查负载均衡器是否正确配置了x-forwarded-for头。
q2: 多级代理下如何确定真实ip?
a: 使用本文提供的parsexforwardedfor方法,它会自动处理多级代理情况。
q3: 如何防止ip伪造?
a: 配置internal-proxies只信任内部代理服务器,不信任客户端传递的头部。
总结
获取真实客户端ip是web开发中的基础但重要的工作。通过本文的完整方案,你可以:
- 正确获取多级代理后的真实客户端ip
- 有效防止ip地址伪造攻击
- 实现ip级别的安全防护
- 具备完整的监控和调试能力
记住:不要相信客户端传递的任何信息,始终通过可信的代理服务器头字段来获取真实ip。
以上就是springboot中获取真实客户端ip的终极方案的详细内容,更多关于springboot获取客户端ip的资料请关注代码网其它相关文章!
发表评论