文章目录
- [一、最终使用效果(@ClientIp 注解获取)](#一、最终使用效果(@ClientIp 注解获取))
- 二、实现代码
Spring 框架没有现成工具可以方便提取客户端的IP地址,普遍做法就是通过 HttpServletRequest 的 getRemoteAddr 方法获取IP地址。
存在以下问题:
1.proxy:部分客户端使用代理后此方法返回的是代理网络的IP地址,非用户真实 IP
2.SLB:后台经过负载均衡,如阿里云的SLB实例 3,方法返回地址是SLB实例 IP,并非用户真实 IP
3.环回地址:在本地测试时获取到的是ipv4:127.0.0.1 或者 ipv6:0:0:0:0:0:0:0:1,并非本机分配地址
4.代码简洁与耦合:每次获取地址都需要注入 HttpServletRequest 再提取,使用 Spring WebFlux 1 而不是Spring MVC,没有此对象可用
5.获取地址可能是IPv6 地址,长度不同,数据库需要兼容处理,适配以后 IPv6需求
问题解决:1.proxy :经过代理后通常可用通过 http header 的 Proxy-Client-IP 获取用户真实 IP地址
2.SLB:经过SLB实例后可通过 http header 的 X-Forwarded-For 获取用户真实IP
3.环回地址:如果是环回地址,则根据网卡取本机配置的IP,如192.168.199.123 等
4.代码简洁与耦合:实现参数解析器,使用注解方式获取IP,如 @ClientIp
5.不同版本 IP 长度不同,取最长作为数据库存储长度(47最长)
一、最终使用效果(@ClientIp 注解获取)
java
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;
@Slf4j
@RestController
@RequestMapping("/test")
@EnableAutoConfiguration
public class OrderController {
@GetMapping("/hello")
@ResponseBody
@ResponseStatus(HttpStatus.OK)
public String hello(@ClientIp String ip) {
return "hello, ip = " + ip;
}
}
二、实现代码
注:下面为 Spring MVC 下的实现代码,如需在Spring webFlux 下使用,同理实现下面方法、配置即可
org.springframework.web.reactive.result.method.HandlerMethodArgumentResolver
org.springframework.web.reactive.config.WebFluxConfigurer
1.注解
java
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface ClientIp {
}
2.方法参数解析器(Resolver)
java
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.MethodParameter;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
import javax.servlet.ServletRequest;
import java.net.InetAddress;
import java.net.UnknownHostException;
public class ClientIpResolver implements HandlerMethodArgumentResolver {
private static final String[] IP_HEADER_CANDIDATES = {
"X-Forwarded-For",
"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"
};
@Override
public boolean supportsParameter(MethodParameter param) {
return param.getParameterType().equals(String.class) &&
param.hasParameterAnnotation(ClientIp.class);
}
@Override
public Object resolveArgument(MethodParameter parameter,
ModelAndViewContainer mavContainer,
NativeWebRequest webRequest,
WebDataBinderFactory binderFactory) {
// 提取header得到IP地址列表(多重代理场景),取第一个IP
for (String header : IP_HEADER_CANDIDATES) {
String ipList = webRequest.getHeader(header);
if (ipList != null && ipList.length() != 0 &&
!"unknown".equalsIgnoreCase(ipList)) {
return ipList.split(",")[0];
}
}
// 没有经过代理或者SLB,直接 getRemoteAddr 方法获取IP
String ip = ((ServletRequest) webRequest.getNativeRequest()).getRemoteAddr();
// 如果是本地环回IP,则根据网卡取本机配置的IP
if ("127.0.0.1".equals(ip) || "0:0:0:0:0:0:0:1".equals(ip)) {
try {
InetAddress inetAddress = InetAddress.getLocalHost();
return inetAddress.getHostAddress();
} catch (UnknownHostException e) {
e.printStackTrace();
return ip;
}
}
return ip;
}
}
3.全局增加Resolver配置
java
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.util.List;
@Configuration
public class NetWebMvcConfigurer implements WebMvcConfigurer {
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(clientIpResolver());
}
@Bean
public ClientIpResolver clientIpResolver() {
return new ClientIpResolver();
}
}