通过反射访问HttpURLConnection的私有MessageHeader字段(&Q;Autorization&Q;)
目前正在为现有库编写单元测试,我正在尝试解决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
:
但我肯定遗漏了什么--有人能说出是什么吗?
更新
让我困惑的是
- 我只从
java.net
包导入URLConnection
和HttpURLConnection
这一事实。然而,从第一个调试屏幕截图来看,conn
-对象实现显然来自sun.net.www.protocol.https
。 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 "";
}
}
相关文章