通过反射访问HttpURLConnection的私有MessageHeader字段(&Q;Autorization&Q;)

2022-05-07 00:00:00 reflection java-8 java httpurlconnection

目前正在为现有库编写单元测试,我正在尝试解决here的限制,即您无法使用反射检索已设置的授权和标头。

我使用的代码是一个非常典型的代码片段,我已经使用了几十次来访问私有字段:

HttpURLConnection conn = (HttpURLConnection) new URL("https://stackoverflow.com").openConnection();
conn.setRequestProperty("Authorization", "Basic Zm9vYmFyOnNlY3JldA==");
try {
    Field requests = conn.getClass().getSuperclass().getSuperclass().getSuperclass().getDeclaredField("requests");
    requests.setAccessible(true);
    MessageHeader headers = (MessageHeader) requests.get(conn); // Problem: returns null
    return headers.getValue(headers.getKey("Authorization"));
} catch (IllegalAccessException | NoSuchFieldException e) {
    e.printStackTrace();
}

但是-通过Field::get提取失败并返回null(请参阅注释行)。

查看HttpUrlConnection的基类URLConnection,我知道我正在寻找requests字段。通过它调试,我可以看到我想要提取的字段(甚至显示了授权和值):

在无法返回MessageHeader对象的代码行中,看起来我引用了URLConnection

中的字段

但我肯定遗漏了什么--有人能说出是什么吗?

更新

让我困惑的是

  1. 我只从java.net包导入URLConnectionHttpURLConnection这一事实。然而,从第一个调试屏幕截图来看,conn-对象实现显然来自sun.net.www.protocol.https
  2. DelegateHttpsURLConnection成员(也显示在第一个调试截图中)

解决方案

当您在对象上调用getClass()时,您将获得其实际的运行时类型,该类型不必与编译时类型匹配,因为它可能是它的子类。因此,通过getSuperclass()遍历类层次结构是很脆弱的。

当您知道预先声明了字段的类时,您可以只使用该类,例如

HttpURLConnection conn = (HttpURLConnection)
    new URL("https://stackoverflow.com").openConnection();
try {
  Field f = URLConnection.class.getDeclaredField("requests");
  f.setAccessible(true);
  System.out.println(f.get(conn));
}
catch(ReflectiveOperationException ex) {
  ex.printStackTrace();
}
但是,这将始终打印null,因为这个特定的连接实现不使用其超类状态,而是委托给不同的实现。正如您自己发现的那样,它存储在一个名为delegate的字段中。

HttpURLConnection conn = (HttpURLConnection)
    new URL("https://stackoverflow.com").openConnection();
for(Class<?> c = conn.getClass(); c != URLConnection.class; c = c.getSuperclass())
  System.out.print(c.getName() + " > ");
System.out.println(URLConnection.class.getName());

Field delegate = conn.getClass().getDeclaredField("delegate");
delegate.setAccessible(true);
conn = (HttpURLConnection)delegate.get(conn);

for(Class<?> c = conn.getClass(); c != URLConnection.class; c = c.getSuperclass())
  System.out.print(c.getName() + " > ");
System.out.println(URLConnection.class.getName());

Field requests = URLConnection.class.getDeclaredField("requests");
requests.setAccessible(true);
System.out.println(requests.get(conn));

此打印

sun.net.www.protocol.https.HttpsURLConnectionImpl > javax.net.ssl.HttpsURLConnection > java.net.HttpURLConnection > java.net.URLConnection
sun.net.www.protocol.https.DelegateHttpsURLConnection > sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection > sun.net.www.protocol.http.HttpURLConnection > java.net.HttpURLConnection > java.net.URLConnection
null

显示这两个对象具有不同的类层次结构,因此,假设特定深度遍历它确实是脆弱的。

它还会再次打印null,因为即使是这个实现类也没有使用从URLConnection继承的requests字段。相反,类sun.net.www.protocol.http.HttpURLConnection声明了自己的同名字段,其中包含实际的MessageHeader对象。这就是为什么在委托的实现类上执行getSuperclass().getSuperclass()会得到正确的字段,而不是URLConnection的字段。

当我们事先知道这一点时,我们可以使用

HttpURLConnection conn =
    (HttpURLConnection) new URL("https://stackoverflow.com").openConnection();
conn.setRequestProperty("Authorization", "Basic Zm9vYmFyOnNlY3JldA==");

Field delegate = Class.forName("sun.net.www.protocol.https.HttpsURLConnectionImpl")
    .getDeclaredField("delegate");
Field requests = Class.forName("sun.net.www.protocol.http.HttpURLConnection")
    .getDeclaredField("requests");
AccessibleObject.setAccessible(new Field[] { delegate, requests}, true);
sun.net.www.MessageHeader headers =
    (sun.net.www.MessageHeader)requests.get(delegate.get(conn));
return headers.findValue("Authorization");

因为您无论如何都要访问sun.net. …包,所以您可以直接引用这些类,而不是使用Class.forName,但我认为后者在这种特定情况下更可靠,因为所有这些类都很容易混淆名称,有时甚至是相同的简单名称,只是它们的包不同。

private String getAuthorizationHeaderValue(HttpURLConnection conn) {
  try {
    Field delegate = sun.net.www.protocol.https.HttpsURLConnectionImpl.class.getDeclaredField("delegate");
    Field requests = sun.net.www.protocol.http.HttpURLConnection.class.getDeclaredField("requests");
    AccessibleObject.setAccessible(new Field[] { delegate, requests}, true);
    sun.net.www.MessageHeader headers = (sun.net.www.MessageHeader)requests.get(delegate.get(conn));
    return headers.findValue("Authorization");
  } catch(ReflectiveOperationException ex) {
    ex.printStackTrace();
    return "";
  }
}

相关文章