Spring 客戶端 IP 地址獲取及儲存細節
原文連結:Spring 客戶端 IP 地址獲取及儲存細節
已獲得作者授權轉載
本文講解如何在Spring框架內獲取客戶端 IP 地址以及儲存的細節,常見使用場景如下:
- 網路安全,通常需要知道客戶端請求的IP地址,以方便與已有的黑名單等進行對比,從而識別攻擊
- 資料分析,記錄使用者登陸IP地址,識別使用者地理位置,統計各省市使用者數量等
- 請求限制,記錄請求IP地址,限制請求頻率
Spring 框架沒有現成工具可以方便提取客戶端的IP地址,普遍做法就是通過 HttpServletRequest
的 getRemoteAddr
方法獲取IP地址。
存在以下問題:
- proxy:部分客戶端使用代理後此方法返回的是代理網路的IP地址,非使用者真實 IP
- SLB:後臺經過負載均衡,如阿里雲的SLB例項,方法返回地址是SLB例項 IP,並非使用者真實 IP
- 環回地址:在本地測試時獲取到的是ipv4:127.0.0.1 或者 ipv6:0:0:0:0:0:0:0:1,並非本機分配地址
- 程式碼簡潔與耦合:每次獲取地址都需要注入 HttpServletRequest 再提取,使用 Spring WebFlux 而不是Spring MVC,沒有此物件可用
- 獲取地址可能是IPv6 地址,長度不同,資料庫需要相容處理,適配以後 IPv6需求
問題解決:
- proxy :經過代理後通常可用通過 http header 的 Proxy-Client-IP 獲取使用者真實 IP地址
- SLB:經過SLB例項後可通過 http header 的 X-Forwarded-For 獲取使用者真實IP
- 環回地址:如果是環回地址,則根據網路卡取本機配置的IP,如192.168.199.123 等
- 程式碼簡潔與耦合:實現引數解析器,使用註解方式獲取IP,如
@ClientIp
- 不同版本 IP 長度不同,取最長作為資料庫儲存長度(47最長)
| 版本 | 例子 | 字元長度 | | ---------------- | --------------------------------------------- | -------- | | IPv4 | 192.168.199.111 | 15 | | IPv6 | ABCD:ABCD:ABCD:ABCD:ABCD:ABCD:ABCD:ABCD | 39 | | IPv4-mapped IPv6 | ABCD:ABCD:ABCD:ABCD:ABCD:ABCD:192.168.158.190 | 45 |
注:IPv6 前後可能用:: 描述部分段,會增加2個字元,見 rfc6052
參考Linux系統下 inet.h 檔案
```c
define INET_ADDRSTRLEN (16)
define INET6_ADDRSTRLEN (48)
``` 最後一個字元為終結符,不算在內,最長為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
註解
```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 {
} ```
方法引數解析器(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;
}
}
```
全域性增加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();
}
} ```
參考文件
相關文章
- ASP.NET獲取客戶端IP及MAC地址ASP.NET客戶端Mac
- 服務端如何獲取客戶端請求IP地址服務端客戶端
- 獲取客戶端Mac地址客戶端Mac
- JS獲取客戶端IP地址與機器名JS客戶端
- 在SelfHost專案中獲取客戶端IP地址客戶端
- pomelo獲取客戶端IP客戶端
- JSF/JAVA 根據IP獲取客戶端Mac地址JSJava客戶端Mac
- 獲取SQL Server中連線的客戶端IP地址SQLServer客戶端
- 在OwinSelfHost專案中獲取客戶端IP地址客戶端
- c# 獲取客戶端IPC#客戶端
- 獲取客戶端真實IP客戶端
- .net 獲取客戶端真實ip客戶端
- java 獲取客戶端真實ipJava客戶端
- java獲取客戶端ip和macJava客戶端Mac
- asp.net 獲取客戶端瀏覽器訪問的IP地址ASP.NET客戶端瀏覽器
- 在Intranet中獲得客戶端IP地址客戶端
- javascript獲取客戶端ip地址省市和運營商程式碼例項JavaScript客戶端
- 一次獲取客戶端 IP 記錄客戶端
- 伺服器獲取真實客戶端 IP伺服器客戶端
- java web 通過request獲取客戶端IPJavaWeb客戶端
- 【知識積累】伺服器端獲取客戶端的IP地址(當客戶端呼叫由Axis開發的WebService)伺服器客戶端Web
- Java面試題-如何獲取客戶端真實IPJava面試題客戶端
- 客戶端儲存筆記客戶端筆記
- 客戶端儲存那些事客戶端
- oracle取客戶端網路卡地址Oracle客戶端
- Nginx 反向代理後如何獲取真實客戶端 IPNginx客戶端
- Silverlight中利用WCF獲取客戶端IP客戶端
- 監控database上的客戶端ip地址Database客戶端
- 客戶端資料儲存概述客戶端
- 阿里雲CDN + nginx多級代理獲取客戶端IP阿里Nginx客戶端
- nginx反向代理獲取客戶端的真實IP和域名Nginx客戶端
- nginx多級代理下如何獲取客戶端真實IPNginx客戶端
- 在容器服務中獲取客戶端真實源 IP客戶端
- 《客戶端儲存技術》總結客戶端
- saltstack獲取IP地址
- 獲取IP地址方法
- 獲取IP地址命令
- Nacos - 客戶端例項列表獲取客戶端