通过nginx代理,feign远程调用后获取客户端真实ip地址等参数

请求过程

 
用户在本地通过浏览器页面发起请求,请求经过nginx代理,转发到SpringCloud框架的微服务A模块,A服务模块通过Feign调用服务提供者B模块
 

实现功能

 
在B模块中想要获取到用户主机的真实ip地址,以及浏览器页面上的请求路径
 

遇到问题

1、由于经过nginx代理,直接通过request.getRemoteAddr();获取到的是nginx服务器的地址,而不是客户端的真实ip地址
2、由于经过了feign请求,直接通过request.getRequestURL();获取到的是请求B模块的url路径,而不是客户端请求的路径。
 
 

解决方法

通过修改nginx的配置文件、将客户端的真实ip地址,客户端的真实请求路径放入请求的header中,在B模块获取各种参数时,通过获取request的请求头进而获取。
 

1、修改nginx配置文件,并刷新配置

 
在匹配的location {}模块中,添加如下参数
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_set_header            uri  $uri;
proxy_set_header            client_origin  $http_origin;

 

通过nginx代理,feign远程调用后获取客户端真实ip地址等参数_第1张图片
 
2、在项目中进行接收
 
String host = "127.0.0.1:8888";
String ip = "127.0.0.1";
String uri = "127.0.0.1:8888/login";
HttpServletRequest request = null;
RequestAttributes requestAttributes = RequestContextHolder.currentRequestAttributes();




if (requestAttributes != null) {
    request = ((ServletRequestAttributes) requestAttributes).getRequest();
    if (request!=null){
        ip = HttpClientUtil.getInstance().getClientIP(request);
        if (request.getLocalAddr()!=null&&!"".equals(request.getLocalAddr())){
            host = request.getLocalAddr() + ":" + request.getLocalPort();
        }
        String origin = request.getHeader("client_origin");
        String client_uri = request.getHeader("uri");
        if (origin!=null&&!"".equals(origin)&&client_uri!=null&&!"".equals(client_uri)){
            uri = origin + client_uri;
        }
    }
}
requestMap.put("host",host);
requestMap.put("client_ip",ip);
requestMap.put("uri",uri);

 

getClientIP是自己写的一个帮助方法,如下

/**
* 获取真实的客户端ip
* @param request
* @return
*/
public String getClientIP(HttpServletRequest request){
    String ip = request.getHeader("X-Forwarded-For");
    if (!StringUtils.isEmpty(ip) && !"unKnown".equalsIgnoreCase(ip)) {
        //多次反向代理后会有多个ip值,第一个ip才是真实ip
        int index = ip.indexOf(",");
        if (index != -1) {
            return ip.substring(0, index);
        } else {
            return ip;
        }
    }
    ip = request.getHeader("X-real-ip");
    if (!StringUtils.isEmpty(ip) && !"unKnown".equalsIgnoreCase(ip)) {
        return ip;
    }
    return request.getRemoteAddr();
}

 

 
    一般来说,经过上面的设置之后就可以取到nginx中设置的header头部参数了,但是我取到的值竟然为null
    后来经过查证才明白,在通过feign进行服务的远程调用时,会把header给丢失了,纳尼?为什么?这不是难为我这个18岁的老同志吗。我劝feign这个年轻人要耗子尾汁,但是没有办法,bug还是要修复的。
 

解决办法

 
    在微服务A模块,及即eign远程调用的消费者端,添加一个拦截器,对A服务接收到的request请求进行拦截,将其加入到RequestTemplate中去
 

拦截器


package com.github.wxiaoqi.security.auth.configuration;


import feign.RequestInterceptor;
import feign.RequestTemplate;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;


import javax.servlet.http.HttpServletRequest;
import java.util.Enumeration;
@Slf4j
@Configuration
public class FeginHeaderInterceptor implements RequestInterceptor {

    @Override
    public void apply(RequestTemplate template) {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder
                .getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        Enumeration headerNames = request.getHeaderNames();
        if (headerNames != null) {
            while (headerNames.hasMoreElements()) {
                String name = headerNames.nextElement();
                String values = request.getHeader(name);
                template.header(name, values);
            }
        }
        Enumeration bodyNames = request.getParameterNames();
        StringBuffer body =new StringBuffer();
        if (bodyNames != null) {
            while (bodyNames.hasMoreElements()) {
                String name = bodyNames.nextElement();
                String values = request.getParameter(name);
                body.append(name).append("=").append(values).append("&");
            }
        }
        if(body.length()!=0) {
            body.deleteCharAt(body.length()-1);
            template.body(body.toString());
        }
    }




}
 

 

配置拦截器

在A模块,即feign消费者的远程接口调用方法中,设置此拦截器
 
@FeignClient(value = "ace-admin",configuration = FeginHeaderInterceptor.class)
public interface IUserService {
  @RequestMapping(value = "/api/user/validate", method = RequestMethod.POST)
  public UserInfo validate(@RequestBody JwtAuthenticationRequest authenticationRequest);
}

 

    以上的配置完成后,就可以实现在feign的微服务提供者端,获取真实客户端的各种参数了。我看有的人说还需要 指定Feign的隔离策略为:SEMAPHORE,因为ThreadLocal什么的,但是我没有设置就成功了,所以大家可以自己尝试下
    其实无论是通过nginx代理获取到真实ip地址,还是feign远程调用导出的header丢失问题,大家都有分享自己的处理方法。但是像我这种倒霉的,既遇到nginx代理导致真实ip被覆盖,又因为feign导致header丢失的情况,没有太多人出现,所以就总结一下吧!
    不过还是有很多的疑惑,比如:nginx里有哪些$开头的参数,feign为什么会把header丢失了,如果大家知晓的话,还请赐教。
 
 
 
 
 
 

你可能感兴趣的:(Spring,Cloud,nginx,spring,spring,cloud,java)