【异常】使用RestTemplate调用Http请求,赋值host属性无效问题

问题背景

https://stackoverflow.com/questions/9096987/how-to-overwrite-http-header-host-in-a-httpurlconnection/9098440
在与第三方系统联调时,给出的文档需要在 headers中给host属性添加对方指定的值。联调过程中发现调用失败。
使用抓包命令发现headers中host内容并不是程序设置的内容。

抓包语句

tcpdump -i any  host 1.1.1.1 and  port 8445 -A -vvv  --过滤ip为1.1.1.1端口为8445的消息,可以看到headers内容

赋值失效原因

  • 使用RestTemplate调用http请求时,Jdk代码会在设置headers时进行一些受限的校验,判断是否进行限制校验
    • 受限制的key包括:"Access-Control-Request-Headers", "Access-Control-Request-Method", "Connection", "Content-Length", "Content-Transfer-Encoding", "Host", "Keep-Alive", "Origin", "Trailer", "Transfer-Encoding", "Upgrade", "Via"
    • 标记是否进行校验的key的名:sun.net.http.allowRestrictedHeaders,默认false
  • 如果是要进行校验,并且key在上述范围之内,在构建headers时不会进行赋值,使用系统默认值。

解决方案

在启动类 添加代码

System.setProperty("sun.net.http.allowRestrictedHeaders", "true");

启动类中添加是因为http校验会在初始化的时候给allowRestrictedHeaders属性赋值


代码分析

使用RestTemplate 进行http调用,jdk在sun.net.www.protocol.http.HttpURLConnection#isRestrictedHeader方法中进行参数校验

调用链

image
主要涉及

  • isRestrictedHeader:是否限制的header信息
  • isExternalMessageHeaderAllowed:是否允许添加的header信息
  • addRequestProperty:添加请求参数

源码

//调用代码
public class MovieController {


    @Resource
    private RestTemplate restTemplate;

    @GetMapping("/user/{id}")
    public User findById(@PathVariable("id") Long id){
//        restTemplate.headForHeaders("host","123123");
        return restTemplate.getForObject("http://localhost:8001/"+id, User.class);
    }
}


//jdk代码
public class HttpURLConnection extends java.net.HttpURLConnection {
        .....

   static {
		....
		//类初始化时,赋值 allowRestrictedHeaders
        allowRestrictedHeaders = (Boolean)AccessController.doPrivileged(new GetBooleanAction("sun.net.http.allowRestrictedHeaders"));
		....
    }

    //添加的headers属性
    public synchronized void addRequestProperty(String var1, String var2) {
        if (!this.connected && !this.connecting) {
            if (var1 == null) {
                throw new NullPointerException("key is null");
            } else {
                if (this.isExternalMessageHeaderAllowed(var1, var2)) { //校验当前值是否允许添加
                    this.requests.add(var1, var2);//给headers添加程序的赋值
                    if (!var1.equalsIgnoreCase("Content-Type")) {
                        this.userHeaders.add(var1, var2);
                    }
                }

            }
        } else {
            throw new IllegalStateException("Already connected");
        }
    }
    //是否允许添加的header信息
    private boolean isExternalMessageHeaderAllowed(String var1, String var2) {
        this.checkMessageHeader(var1, var2);
        return !this.isRestrictedHeader(var1, var2);
    }
    //是否限制的header信息
    private boolean isRestrictedHeader(String var1, String var2) {
        if (allowRestrictedHeaders) {//是否需要进行校验  默认false ,可以通过系统参数进行配置
            return false;
        } else {
            var1 = var1.toLowerCase();
               //restrictedHeaderSet中的值  【Access-Control-Request-Headers", "Access-Control-Request-Method", "Connection", "Content-Length", "Content-Transfer-Encoding", "Host", "Keep-Alive", "Origin", "Trailer", "Transfer-Encoding", "Upgrade", "Via】,如果在这个范围,就不进行赋值
            if (restrictedHeaderSet.contains(var1)) {
                return !var1.equals("connection") || !var2.equalsIgnoreCase("close");
            } else {
                return var1.startsWith("sec-");
            }
        }
    }
        .....
}


posted @ 2025-03-08 21:52  此木|西贝  阅读(52)  评论(0)    收藏  举报