如何获取操作人IP?

如何获取操作人IP?

_

要在日志中记录操作人的真实IP,核心是区分部署场景(直连/反向代理) 并正确提取IP(处理代理转发的请求头),以下分「通用原理」「主流语言/框架实现」「注意事项」三部分说明:

一、通用原理

部署场景 核心逻辑
直连应用服务器 直接获取TCP连接的远端IP(如 REMOTE_ADDR
反向代理(Nginx/CDN/APACHE) 代理会将真实客户端IP写入请求头(如 X-Forwarded-For/X-Real-IP),需优先从这些头提取

关键请求头说明:

  • X-Forwarded-For:格式为 客户端IP, 代理1IP, 代理2IP(多个代理时,第一个是真实客户端IP);
  • X-Real-IP:直接记录真实客户端IP(单代理场景常用);
  • REMOTE_ADDR:默认是「直接连接应用的节点IP」(代理场景下是代理IP,非客户端真实IP)。

二、主流语言/框架实现

1. Java(Spring Boot/Spring MVC)

步骤1:配置Nginx(代理场景)

若使用Nginx反向代理,先在 nginx.conf中添加请求头转发:

server {
    listen 80;
    server_name your-domain.com;

    # 转发真实IP到应用
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

    location / {
        proxy_pass http://127.0.0.1:8080; # 应用地址
    }
}
步骤2:封装IP获取工具类
import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Pattern;

public class IpUtils {
    // 排除内网/保留IP(可选,根据业务需要)
    private static final Pattern INNER_IP_PATTERN = Pattern.compile(
        "^(127\\.0\\.0\\.1)|(localhost)|(10\\.\\d+\\.\\d+\\.\\d+)|(172\\.((1[6-9])|(2\\d)|(3[0-1]))\\.\\d+\\.\\d+)|(192\\.168\\.\\d+\\.\\d+)|(::1)|(fe80::.*)$"
    );

    /**
     * 获取真实客户端IP
     */
    public static String getRealIp(HttpServletRequest request) {
        // 1. 优先取X-Forwarded-For
        String xff = request.getHeader("X-Forwarded-For");
        if (isValidIp(xff)) {
            // 拆分逗号分隔的IP列表,取第一个有效IP
            List<String> ipList = Arrays.asList(xff.split(","));
            for (String ip : ipList) {
                ip = ip.trim();
                if (isValidIp(ip) && !INNER_IP_PATTERN.matcher(ip).matches()) {
                    return ip;
                }
            }
        }

        // 2. 取X-Real-IP
        String xri = request.getHeader("X-Real-IP");
        if (isValidIp(xri)) {
            return xri.trim();
        }

        // 3. 最后取REMOTE_ADDR
        return request.getRemoteAddr();
    }

    /**
     * 校验IP是否有效(非空/非unknown)
     */
    private static boolean isValidIp(String ip) {
        return ip != null && !ip.isEmpty() && !"unknown".equalsIgnoreCase(ip);
    }
}
步骤3:在业务中使用(记录日志)
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@RestController
public class OperationController {
    private static final Logger log = LoggerFactory.getLogger(OperationController.class);

    @GetMapping("/operate")
    public String operate(HttpServletRequest request) {
        String realIp = IpUtils.getRealIp(request);
        // 记录到日志(可结合MDC实现全链路日志关联)
        log.info("操作人IP:{},执行了XXX操作", realIp);
        return "success";
    }
}

2. Python

(1)Django
import re
from django.http import HttpRequest

# 内网IP正则
INNER_IP_PATTERN = re.compile(
    r"^(127\.0\.0\.1)|(localhost)|(10\.\d+\.\d+\.\d+)|(172\.((1[6-9])|(2\d)|(3[0-1]))\.\d+\.\d+)|(192\.168\.\d+\.\d+)|(::1)|(fe80::.*)$"
)

def get_real_ip(request: HttpRequest) -> str:
    """获取真实IP"""
    # 1. 优先取X-Forwarded-For
    xff = request.META.get('HTTP_X_FORWARDED_FOR', '')
    if xff and xff != 'unknown':
        # 拆分第一个有效IP
        ip = xff.split(',')[0].strip()
        if ip and not INNER_IP_PATTERN.match(ip):
            return ip
  
    # 2. 取X-Real-IP
    xri = request.META.get('HTTP_X_REAL_IP', '')
    if xri and xri != 'unknown':
        return xri.strip()
  
    # 3. 最后取REMOTE_ADDR
    return request.META.get('REMOTE_ADDR', 'unknown')

# 业务中使用(记录日志)
import logging
logger = logging.getLogger(__name__)

def operate(request):
    real_ip = get_real_ip(request)
    logger.info(f"操作人IP:{real_ip},执行了XXX操作")
    return {"code": 200, "msg": "success"}
(2)Flask
import re
from flask import request
import logging

logger = logging.getLogger(__name__)
INNER_IP_PATTERN = re.compile(
    r"^(127\.0\.0\.1)|(localhost)|(10\.\d+\.\d+\.\d+)|(172\.((1[6-9])|(2\d)|(3[0-1]))\.\d+\.\d+)|(192\.168\.\d+\.\d+)|(::1)|(fe80::.*)$"
)

def get_real_ip():
    """获取真实IP"""
    # 1. X-Forwarded-For
    xff = request.headers.get('X-Forwarded-For', '')
    if xff and xff != 'unknown':
        ip = xff.split(',')[0].strip()
        if ip and not INNER_IP_PATTERN.match(ip):
            return ip
  
    # 2. X-Real-IP
    xri = request.headers.get('X-Real-IP', '')
    if xri and xri != 'unknown':
        return xri.strip()
  
    # 3. remote_addr
    return request.remote_addr

# 接口中使用
@app.route('/operate')
def operate():
    real_ip = get_real_ip()
    logger.info(f"操作人IP:{real_ip},执行了XXX操作")
    return "success"

3. Node.js(Express)

const express = require('express');
const app = express();
const logger = require('winston'); // 或其他日志库

// 信任代理(关键:让Express识别代理转发的IP)
app.set('trust proxy', true); // 生产环境建议指定代理IP段,如 ['192.168.1.0/24']

// 内网IP校验
const isInnerIp = (ip) => {
  return /^(127\.0\.0\.1)|(localhost)|(10\.\d+\.\d+\.\d+)|(172\.((1[6-9])|(2\d)|(3[0-1]))\.\d+\.\d+)|(192\.168\.\d+\.\d+)|(::1)|(fe80::.*)$/.test(ip);
};

// 获取真实IP
const getRealIp = (req) => {
  // 1. X-Forwarded-For
  let xff = req.headers['x-forwarded-for'];
  if (xff) {
    const ipList = xff.split(',').map(ip => ip.trim());
    for (const ip of ipList) {
      if (ip && !isInnerIp(ip)) {
        return ip;
      }
    }
  }

  // 2. X-Real-IP
  let xri = req.headers['x-real-ip'];
  if (xri && !isInnerIp(xri)) {
    return xri;
  }

  // 3. req.ip(Express trust proxy后自动处理)
  return req.ip;
};

// 业务接口
app.get('/operate', (req, res) => {
  const realIp = getRealIp(req);
  logger.info(`操作人IP:${realIp},执行了XXX操作`);
  res.send('success');
});

app.listen(3000);

4. PHP

<?php
/**
 * 获取真实IP
 */
function getRealIp() {
    // 内网IP正则
    $innerIpPattern = '/^(127\.0\.0\.1)|(localhost)|(10\.\d+\.\d+\.\d+)|(172\.((1[6-9])|(2\d)|(3[0-1]))\.\d+\.\d+)|(192\.168\.\d+\.\d+)|(::1)|(fe80::.*)$/';

    // 1. X-Forwarded-For
    $xff = $_SERVER['HTTP_X_FORWARDED_FOR'] ?? '';
    if ($xff && $xff !== 'unknown') {
        $ipList = explode(',', $xff);
        $ip = trim($ipList[0]);
        if ($ip && !preg_match($innerIpPattern, $ip)) {
            return $ip;
        }
    }

    // 2. X-Real-IP
    $xri = $_SERVER['HTTP_X_REAL_IP'] ?? '';
    if ($xri && $xri !== 'unknown') {
        return trim($xri);
    }

    // 3. REMOTE_ADDR
    return $_SERVER['REMOTE_ADDR'] ?? 'unknown';
}

// 记录日志
$realIp = getRealIp();
error_log("操作人IP:{$realIp},执行了XXX操作");
echo "success";
?>

三、关键注意事项

  1. 防止IP伪造

    • 生产环境需限制「仅信任前端代理的IP段」(如Spring Boot配置 server.forward-headers-strategy=native、Express指定 trust proxy: ['192.168.1.0/24']),避免恶意用户伪造 X-Forwarded-For头;
    • 不要直接使用 X-Forwarded-For的第一个值,需过滤内网IP/无效值。
  2. IPv6兼容

    • 注意IPv6地址格式(如 ::1是本地回环,240e::xxx是公网IPv6),正则需包含IPv6规则。
  3. 日志规范

    • 建议将IP放入日志的结构化字段(如JSON格式的 client_ip),便于后续日志分析/检索;
    • 结合MDC(Java)/context(Python)实现「请求ID+IP+操作」全链路关联。
  4. 特殊场景

    • 客户端使用代理/VPN时,获取的是代理/VPN的IP(无法获取真实物理IP,属于正常现象);
    • 内网系统可忽略公网IP校验,直接记录内网IP即可。
如何获取操作来源端 2025-12-15

评论区