如何在Spring MVC控制器的GET调用中提取IP地址?

146

我正在开发一个Spring MVC控制器项目,其中我从浏览器中发出了一个GET URL调用 -

以下是我从浏览器中发出GET调用的URL-

http://127.0.0.1:8080/testweb/processing?workflow=test&conf=20140324&dc=all

以下是在浏览器中点击后发起调用的代码 -

@RequestMapping(value = "processing", method = RequestMethod.GET)
public @ResponseBody ProcessResponse processData(@RequestParam("workflow") final String workflow,
    @RequestParam("conf") final String value, @RequestParam("dc") final String dc) {

        System.out.println(workflow);
        System.out.println(value);
        System.out.println(dc);

        // some other code
    }

问题陈述:

现在有什么方法可以从某个头部提取IP地址吗?也就是说,我想知道调用上面URL的人使用的IP地址是什么。这是否可能做到?


尝试使用Spring AOP并处理HttpServletRequest。https://dev59.com/omIk5IYBdhLWcg3wbtyb - Dilip G
11个回答

191

解决方案是

@RequestMapping(value = "processing", method = RequestMethod.GET)
public @ResponseBody ProcessResponse processData(@RequestParam("workflow") final String workflow,
    @RequestParam("conf") final String value, @RequestParam("dc") final String dc, HttpServletRequest request) {

        System.out.println(workflow);
        System.out.println(value);
        System.out.println(dc);
        System.out.println(request.getRemoteAddr());
        // some other code
    }
HttpServletRequest request添加到您的方法定义中,然后使用Servlet API。
Spring文档在这里中提到:
 

15.3.2.3支持的处理程序方法参数和返回类型

Handler methods that are annotated with @RequestMapping can have very flexible signatures.
Most of them can be used in arbitrary order (see below for more details).

Request or response objects (Servlet API). Choose any specific request or response type,
for example ServletRequest or HttpServletRequest

2
感谢Koitoer的帮助。有一个快速的问题,假设调用来自负载均衡器而不是特定的机器,那么这也可以工作吗?我猜不行.. - john
1
请检查负载均衡器是否可以在头部发送这些值,因此请考虑使用HttpServletRequest的getHeader方法。 - Koitoer
1
如果请求来自Postman,我会得到错误的IP地址(0:0:0:0:0:0:0:1)。有人知道如何在请求来自Postman时获取IP地址吗? - Dipesh Gandhi
1
我也得到了0:0:0:0:0:0:0:1。 - code
1
这已经过时了。 - Gewure
显示剩余4条评论

143

我来晚了,但这可能会帮助正在寻找答案的人。通常情况下,servletRequest.getRemoteAddr() 可以工作。

在许多情况下,你的应用程序用户可能通过代理服务器访问你的 Web 服务器,或者你的应用程序位于负载均衡器后面。

因此,在这种情况下,你应该访问 X-Forwarded-For HTTP 标头来获取用户的 IP 地址。

例如: String ipAddress = request.getHeader("X-FORWARDED-FOR");


15
注意,X-Forwarded-For通常是一个逗号分隔的IP地址列表,每个代理服务器都会将它看到的远程地址添加到该列表中。因此,一个好的实现通常会有一个可信的代理服务器列表,并在从右到左读取该标头时“跳过”这些IP地址。 - Raman
如何获取IP地址为111.111.111.111/X。 - Muneeb Mirza
8
注意,例如 Tomcat 具有 RemoteIpValve,它解析 X-Forwarded-For 标头并将其设置在 HttpServletRequest 上,以便 servletRequest.getRemoteAddr() 返回正确的终端用户 IP 地址。在 Spring Boot 中,可以通过 server.tomcat.remote-ip-header=X-Forwarded-For 应用程序属性启用它。 - Gediminas Rimsa
1
客户端是否可以伪造 X-FORWARDED-FOR 标头?如果 getRemoteAddr() 属于可信代理服务提供商的白名单,那么只假定 X-FORWARDED-FOR 有效是否更安全? - Klesun
Spring Boot 能够自动解决转发的 IP 地址问题。详情请参见 此问题 - icguy

58

我用这种方法来做这件事

public class HttpReqRespUtils {

    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"
    };

    public static String getClientIpAddressIfServletRequestExist() {

        if (RequestContextHolder.getRequestAttributes() == null) {
            return "0.0.0.0";
        }

        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        for (String header: IP_HEADER_CANDIDATES) {
            String ipList = request.getHeader(header);
            if (ipList != null && ipList.length() != 0 && !"unknown".equalsIgnoreCase(ipList)) {
                String ip = ipList.split(",")[0];
                return ip;
            }
        }

        return request.getRemoteAddr();
    }
}

2
我在哪里使用它? - Maifee Ul Asad
这是一个静态类,它使用类名和方法:ip = HttpReqRespUtils.getClientIpAddressIfServletRequestExist(); - Manuel Larrota
1
对于那些想知道为什么当 RequestContextHolder.getRequestAttributes() == null 时返回 0.0.0.0 的人,请参见 0.0.0.0 - Wikipedia - Jason Law
1
在上述代码中,在return ip语句之前添加if (ip.equals("0:0:0:0:0:0:0:1")) ip = "127.0.0.1";以使其符合IPv4标准。 - Muhammad Tariq

19
您可以从RequestContextHolder静态地获取IP地址,如下所示:
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes())
        .getRequest();

String ip = request.getRemoteAddr();

8

在我的案例中,我在应用程序前使用了Nginx,并采用以下配置:

location / {
     proxy_pass        http://localhost:8080/;
     proxy_set_header  X-Real-IP $remote_addr;
     proxy_set_header  X-Forwarded-For $proxy_add_x_forwarded_for;
     proxy_set_header  Host $http_host;
     add_header Content-Security-Policy 'upgrade-insecure-requests';
}

因此在我的应用程序中,我可以这样获取真实的用户IP:

String clientIP = request.getHeader("X-Real-IP");

1
谢谢您先生。这是一个优雅的解决方案,帮助了我。 - Andrew G

7

请参见下方代码。此代码适用于spring-boot和spring-boot + apache CXF/SOAP。

    // in your class RequestUtil
    private static final String[] IP_HEADER_NAMES = { 
                                                        "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"
                                                    };

    public static String getRemoteIP(RequestAttributes requestAttributes)
    {
        if (requestAttributes == null)
        {
            return "0.0.0.0";
        }
        HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest();
        String ip = Arrays.asList(IP_HEADER_NAMES)
            .stream()
            .map(request::getHeader)
            .filter(h -> h != null && h.length() != 0 && !"unknown".equalsIgnoreCase(h))
            .map(h -> h.split(",")[0])
            .reduce("", (h1, h2) -> h1 + ":" + h2);
        return ip + request.getRemoteAddr();
    }

    //... in service class:
    String remoteAddress = RequestUtil.getRemoteIP(RequestContextHolder.currentRequestAttributes());

:)


6

以下是Spring的方式,在@Controller类中使用autowired请求bean:

@Autowired 
private HttpServletRequest request;

System.out.println(request.getRemoteHost());

4
这可能会导致控制器在多个线程中同时使用时出现问题。仅应使用@Autowired将单例注入到@Controller实例中。否则,您必须在控制器类上使用@Scope(BeanDefinition.SCOPE_PROTOTYPE),以确保每个请求都创建一个新的控制器实例。这样做效率较低,但如果您必须将某些内容作为属性注入到控制器类中,则可以使用此方法解决问题。 - Andy

6
把这个方法放在你的BaseController中:
@SuppressWarnings("ConstantConditions")
protected String fetchClientIpAddr() {
    HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.getRequestAttributes())).getRequest();
    String ip = Optional.ofNullable(request.getHeader("X-FORWARDED-FOR")).orElse(request.getRemoteAddr());
    if (ip.equals("0:0:0:0:0:0:0:1")) ip = "127.0.0.1";
    Assert.isTrue(ip.chars().filter($ -> $ == '.').count() == 3, "Illegal IP: " + ip);
    return ip;
}

4
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"
    };

    public static String getIPFromRequest(HttpServletRequest request) {
        String ip = null;
        if (request == null) {
            if (RequestContextHolder.getRequestAttributes() == null) {
                return null;
            }
            request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        }

        try {
            ip = InetAddress.getLocalHost().getHostAddress();
        } catch (Exception e) {
            e.printStackTrace();
        }
        if (!StringUtils.isEmpty(ip))
            return ip;

        for (String header : IP_HEADER_CANDIDATES) {
            String ipList = request.getHeader(header);
            if (ipList != null && ipList.length() != 0 && !"unknown".equalsIgnoreCase(ipList)) {
                return ipList.split(",")[0];
            }
        }

        return request.getRemoteAddr();
    }

我将上述代码组合成了一个适用于大多数情况的代码。将从API获取的HttpServletRequest请求传递到该方法中。

1
在我的情况下,request.getRemoteAddr() 包含用户尝试访问应用程序的系统的 IP 地址。如果我在本地主机或 127.0.0.1 上运行应用程序,则返回 "0:0:0:0:0:0:0:1"。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接