Nginx 实战指南:暴露出请求的真实 IP

在这里插入图片描述

? 嗨,您好 ? 我是 vnjohn,在互联网企业担任 Java 开发,CSDN 优质创作者
? 推荐专栏:Spring、MySQL、Nacos、Java,后续其他专栏会持续优化更新迭代
?文章所在专栏:Linux
? 我当前正在学习微服务领域、云原生领域、消息中间件等架构、原理知识
? 向我询问任何您想要的东西,ID:vnjohn
?觉得博主文章写的还 OK,能够帮助到您的,感谢三连支持博客?
? 代词: vnjohn
⚡ 有趣的事实:音乐、跑步、电影、游戏

目录

前言

在工作中,经常会用用户实际请求的 IP 地址,当需要记录到日志信息时或者在请求其他的第三方接口时需要用到实际的用户 IP 地址传入,在本节中会提供服务端在实际获取 IP 地址的源码以及通过 Nginx 代理后 IP 地址隐藏的问题

用途/场景

获取经纬度

无论是小程序还是 App 端,当用户未开启地理位置授权时,在前端页面是无法拿到当前用户所在的经纬度信息的,这时候还有另外一种途径来获取经纬度,那就是通过 IP 地址来解析用户真实请求时所在的经纬度

在一般情况下,都是由前端来拿到这块请求的 IP,可能由于某些 API 或版本的限制,导致前端在所获取的 IP 地址不准确,此时,不得不让后端的大佬 CASE 出手了

支付接口

在这里插入图片描述

微信 > 小程序支付文档中,终端IP:spbill_create_ip 参数是必填项,虽然在微信那一侧不会去校验这块的 IP 地址是否准确,但真实的 IP 地址有利用我们去排查线上问题,比如 IP 地址都是 127.0.0.1 这样的,你永远都分辨不出到底请求是从那一块服务进来的

白名单配置

当请求第三方接口 API 时,若我们所请求的 IP 地址未配置进白名单时,我们是无法从第三方获取到我们想要得到的信息的,这时候有了前置或后置日志打印我们请求时的 IP,能够更快的帮助我们解决问题

比较友善的第三方 API,在我们请求接口出现白名单的问题,会将我们的 IP 进行返回提示 “该 IP:Xxx,未配置白名单,请联系管理员!”,这时直接拿到返回的错误信息内容进行配置即可,无须任何的日志输入

但是有的第三方 API 就比较鸡肋,只是返回一段 "请求 IP 未配置白名单,请联系管理员!”,这时候如果不+日志打印,那中间去找部署服务的 IP 花费的时间就比这日志打印的时间多了个去

实现源码

请求工具类

/**
 * @author vnjohn
 * @since 2023/10/30
 */
public class IPv4Util {

    private static final String HEADER_FORWARDED_FOR = "x-forwarded-for";
    private static final String HEADER_PROXY_CLIENT_IP = "Proxy-Client-IP";
    private static final String HEADER_WL_PROXY_CLIENT_IP = "WL-Proxy-Client-IP";
    private static final String HEADER_HTTP_CLIENT_IP = "http_client_ip";
    private static final String HEADER_HTTP_X_FORWARDED_FOR = "HTTP_X_FORWARDED_FOR";

    private static final String UNKNOWN = "unknown";
    private static final String CHAR_COLON = ":";
    private static final String CHAR_COMMA = ",";

    public static String getClientIp(HttpServletRequest request) {
        String ip = request.getHeader(HEADER_FORWARDED_FOR);
        boolean ipIsEmpty = ip == null || ip.length() == 0;
        if (ipIsEmpty || UNKNOWN.equalsIgnoreCase(ip)) {
            ip = request.getHeader(HEADER_PROXY_CLIENT_IP);
        }
        if (ipIsEmpty || UNKNOWN.equalsIgnoreCase(ip)) {
            ip = request.getHeader(HEADER_WL_PROXY_CLIENT_IP);
        }
        if (ipIsEmpty || UNKNOWN.equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }
        if (ipIsEmpty || UNKNOWN.equalsIgnoreCase(ip)) {
            ip = request.getHeader(HEADER_HTTP_CLIENT_IP);
        }
        if (ipIsEmpty || UNKNOWN.equalsIgnoreCase(ip)) {
            ip = request.getHeader(HEADER_HTTP_X_FORWARDED_FOR);
        }
        // 如果是多级代理,那么取第一个ip为客户ip
        if (ip != null && ip.contains(CHAR_COMMA)) {
            ip = ip.substring(ip.lastIndexOf(CHAR_COMMA) + 1, ip.length()).trim();
        }
        //判断IP是否存在带有端口号的情况、应该要去掉端口号
        if (ip != null && ip.contains(CHAR_COLON)) {
            ip = ip.substring(0, ip.indexOf(CHAR_COLON));
        }
        return ip;
    }

    /**
     * 获取请求的ip
     */
    public static String getRequestIp() {
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        // 从获取 RequestAttributes 中获取 HttpServletRequest 信息
        HttpServletRequest request = (HttpServletRequest) requestAttributes.resolveReference(RequestAttributes.REFERENCE_REQUEST);
        String ip = request.getHeader("x-forwarded-for");
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_CLIENT_IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_X_FORWARDED_FOR");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }
        return ip;
    }

    public static void main(String[] args) {
        System.out.println(getRequestIp());
    }
}

在其他地方,需要获取 IP 时,只需要按如下传入参数:

HttpServletRequest request

如下调用即可:

IPv4Util.getClientIp(request);

Nginx 代理配置

在 server 标签模块中指定的请求前缀配置一个 location 或直接在 server 标签内进行配置,如下:

location /app {
       proxy_set_header Host $host;
       proxy_set_header X-Real-IP $remote_addr;
       proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
       proxy_pass http://localhost:8084;
}

只有当设置了 proxy_set_header X-Real-IP 代理参数以后,获取服务请求的 IP 才是准确的

小结

注意:只有在 Nginx 代理配置以下内容以后,通过请求工具类才能获取到真实的 IP 地址!!!

总结

该篇博文主要介绍的是如何在服务端中获取用户请求的真实 IP 地址,通过几种用途/场景介绍其所在的必要性:获取经纬度、支付接口、白名单配置,当然,为了让你能够快速的运用起来,提供了请求工具类的源码以及告知你如何在 Nginx 进行代理配置,希望这篇短文能够帮助到您,解决您实际工作中的一些问题,期待三连支持?

???愿你我都能够在寒冬中相互取暖,互相成长,只有不断积累、沉淀自己,后面有机会自然能破冰而行!

博文放在 Linux 专栏里,欢迎订阅,会持续更新!

如果觉得博文不错,关注我 vnjohn,后续会有更多实战、源码、架构干货分享!

推荐专栏:Spring、MySQL,订阅一波不再迷路

大家的「关注❤️ + 点赞? + 收藏⭐」就是我创作的最大动力!谢谢大家的支持,我们下文见!