springboot gateway + nginx + k8s部署如何获取客户端真实ip
springboot gateway + nginx + k8s部署如何获取客户端真实ip。
springboot gateway + nginx + k8s部署如何获取客户端真实ip
新的需求需要对某个接口进行访问ip的限制,但是由于部署环境问题,常用的获取ip方式拿到的都是k8s服务的地址,记录一下在k8s+nginx+gateway的情况下获取真实ip的过程
k8s获取真实ip
通常,当 Kubernetes 集群内的客户端连接到服务的时候,是支持服务的 Pod 可以获取到客户端的 IP 地址的,但是,当通过节点端口接收到连接时,由于对数据包执行了源网络地址转换(SNAT),因此数据包的源 IP 地址会发生变化,后端的 Pod 无法看到实际的客户端 IP,对于某些应用来说是个问题,比如,nginx 的请求日志就无法获取准确的客户端访问 IP 了
通过查看日志就能看到nginx日志中获取到的浏览器访问ip是master节点的内网地址,并不是我们期望的真实的浏览器访问ip
我这里是通过nginx的服务对外开放的访问端口,所以要改这个服务的service
spec:
externalTrafficPolicy: Local
如果 Service 中配置了 externalTrafficPolicy=Local,并且通过服务的节点端口来打开外部连接,则 Service 会代理到本地运行的 Pod,如果本地没有本地 Pod 存在,则连接将挂起,比如我们这里设置上该字段更新,这个时候我们去通过 master 节点的 NodePort 访问应用是访问不到的,因为 master 节点上并没有对应的 Pod 运行,所以需要确保负载均衡器将连接转发给至少具有一个 Pod 的节点。
但是需要注意的是使用这个参数有一个缺点,通常情况下,请求都是均匀分布在所有 Pod 上的,但是使用了这个配置的话,情况就有可能不一样了。比如我们有两个节点上运行了 3 个 Pod,假如节点 A 运行一个 Pod,节点 B 运行两个 Pod,如果负载均衡器在两个节点间均衡分布连接,则节点 A 上的 Pod 将接收到所有请求的 50%,但节点 B 上的两个 Pod 每个就只接收 25% 。
由于增加了externalTrafficPolicy: Local这个配置后,接收请求的节点和目标 Pod 都在一个节点上,所以没有额外的网络跳转(不执行 SNAT),所以就可以拿到正确的客户端 IP
更新服务后,然后再通过 NodePort 访问服务可以看到nginx日志拿到的就是正确的客户端 IP 地址了
ingress的方式没有实践过,查到是把ingress controller的externalTrafficPolicy设为Local,有需要的可以尝试一下
nginx配置
其实这一步不配也是可以的,做完k8s的配置就已经能拿到真实ip了,但有的时候会遇到某个模块的接口里也需要拿真实ip,但是从gateway到某个模块的过程中,在k8s里请求是从gateway的服务转发到相关模块的服务的,这也就导致相关模块获取到的真实ip是gateway服务的ip(或者是nginx服务的ip来着,反正不是上一步拿到的ip)
需要增加如图所示配置,后端获取ip时记得把X-Forwarded-For提到最前面呀,要不就白配置了
我这里的nginx只有一层,至于多层的需要自己测一下是不是需要每层都配还是只配第一层
gateway获取ip
这个就直接上代码吧
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpHeaders;
import org.springframework.http.server.reactive.ServerHttpRequest;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Optional;
@Slf4j
public class WebFluxUtil {
private static final String IP_UNKNOWN = "unknown";
private static final String IP_LOCAL = "127.0.0.1";
private static final String IPV6_LOCAL = "0:0:0:0:0:0:0:1";
public static String getIpAddress(ServerHttpRequest request) {
HttpHeaders headers = request.getHeaders();
String ipAddress = headers.getFirst("x-forwarded-for");
if (ipAddress == null || ipAddress.length() == 0 || IP_UNKNOWN.equalsIgnoreCase(ipAddress)) {
ipAddress = headers.getFirst("Proxy-Client-IP");
}
if (ipAddress == null || ipAddress.length() == 0 || IP_UNKNOWN.equalsIgnoreCase(ipAddress)) {
ipAddress = headers.getFirst("WL-Proxy-Client-IP");
}
if (ipAddress == null || ipAddress.length() == 0 || IP_UNKNOWN.equalsIgnoreCase(ipAddress)) {
ipAddress = headers.getFirst("X-Real-Ip");
}
if (ipAddress == null || ipAddress.length() == 0 || IP_UNKNOWN.equalsIgnoreCase(ipAddress)) {
ipAddress = Optional.ofNullable(request.getRemoteAddress())
.map(address -> address.getAddress().getHostAddress())
.orElse("");
if (IP_LOCAL.equals(ipAddress) || IPV6_LOCAL.equals(ipAddress)) {
// 根据网卡取本机配置的IP
try {
InetAddress inet = InetAddress.getLocalHost();
ipAddress = inet.getHostAddress();
} catch (UnknownHostException e) {
log.error(e.getMessage());
}
}
}
// 对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割
if (ipAddress != null && ipAddress.indexOf(",") > 0) {
ipAddress = ipAddress.split(",")[0];
}
return ipAddress;
}
}
gateway限制访问ip
import com.plat.gateway.config.properties.IgnoreWhiteProperties;
import com.plat.gateway.enums.ExceptionCodeEnum;
import com.plat.gateway.util.WebFluxUtil;
import com.ruoyi.common.core.utils.StringUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
@Component
@Slf4j
public class CheckIPFilter implements GlobalFilter, Ordered {
@Autowired
private IgnoreWhiteProperties ignoreWhiteProperties;
@Override
public int getOrder() {
return 0;
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String ip = WebFluxUtil.getIpAddress(exchange.getRequest());
if (StringUtils.isEmpty(ip)){
ExceptionCodeEnum.GET_IP_ERROR.newException();
}
//对 IP 的访问限制,即不在 IP 白名单中就不能调用受ip访问限制的接口
String path = exchange.getRequest().getURI().getPath();
if (ignoreWhiteProperties.getBlockchain().contains(path)) {
if (!ignoreWhiteProperties.getBlockchainIp().contains(ip)){
log.info("不被允许的ip:{},path:{}",ip,path);
ExceptionCodeEnum.IP_ERROR.newException();
}else
log.info("访问ip:{},path:{}",ip,path);
}
return chain.filter(exchange);
}
}
这里是把nacos里配置的白名单单独放了一个类,这里也写一下吧
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.context.annotation.Configuration;
import java.util.ArrayList;
import java.util.List;
/**
* 放行白名单配置
*/
@Configuration
@RefreshScope
@ConfigurationProperties(prefix = "security.ignore")
public class IgnoreWhiteProperties
{
/**
* 放行白名单配置,网关不校验此处的白名单
*/
private List<String> whites = new ArrayList<>();
/**
* 限制ip访问接口
*/
private List<String> blockchain = new ArrayList<>();
private List<String> blockchainIp = new ArrayList<>();
public List<String> getWhites()
{
return whites;
}
public void setWhites(List<String> whites)
{
this.whites = whites;
}
public List<String> getBlockchain()
{
return blockchain;
}
public void setBlockchain(List<String> blockchain)
{
this.blockchain = blockchain;
}
public List<String> getBlockchainIp()
{
return blockchainIp;
}
public void setBlockchainIp(List<String> blockchainIp)
{
this.blockchainIp = blockchainIp;
}
}
更多推荐
所有评论(0)