在https连接头中设置用户代理属性

12

我无法正确地设置https连接的user-agent属性。据我所知,http-header属性可以通过-Dhttp.agent VM选项或通过URLConnection.setRequestProperty()来设置。然而,通过VM选项设置用户代理会导致“Java/[version]”附加到http.agent的任何值上。同时,setRequestProperty()仅适用于http连接,而不是https(至少在我尝试时是这样的)。

java.net.URL url = new java.net.URL( "https://www.google.com" );
java.net.URLConnection conn = url.openConnection();
conn.setRequestProperty("User-Agent","Mozilla/5.0 (Windows NT 5.1; rv:19.0) Gecko/20100101 Firefox/19.0");
conn.connect();
java.io.BufferedReader serverResponse = new java.io.BufferedReader(new java.io.InputStreamReader(conn.getInputStream()));
System.out.println(serverResponse.readLine());
serverResponse.close();

我通过使用WireShark检查http通信找到/验证了问题。有没有什么办法解决这个问题?更新:附加信息似乎我没有深入研究通信。代码是从代理后面运行的,因此观察到的通信是针对代理设置的(通过-Dhttps.proxyHost),而不是目标网站(google.com)。无论如何,在https连接期间,方法为CONNECT,而不是GET。这是https通信尝试的Wireshark捕获。像我上面提到的,user-agent通过-Dhttp.agent设置,因为URLConnection.setRequestProperty()没有效果(user-agent = Java/1.7.0)。在这种情况下,请注意Java/1.7.0的附加部分。问题仍然是相同的,为什么会发生这种情况,并且如何解决它?
CONNECT www.google.com:443 HTTP/1.1
User-Agent: Mozilla/5.0 (Windows NT 5.1; rv:19.0) Gecko/20100101 Firefox/19.0 Java/1.7.0
Host: www.google.com
Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2
Proxy-Connection: keep-alive

HTTP/1.1 403 Forbidden
X-Bst-Request-Id: MWPwwh:m7d:39175
X-Bst-Info: ch=req,t=1366218861,h=14g,p=4037_7213:1_156,f=PEFilter,r=PEBlockCatchAllRule,c=1905,v=7.8.14771.200 1363881886
Content-Type: text/html; charset=utf-8
Pragma: No-cache
Content-Language: en
Cache-Control: No-cache
Content-Length: 2491

顺便说一下,请求被禁止是因为代理过滤了用户代理,Java/1.7.0导致了拒绝。我已经将Java/1.7.0添加到http连接的用户代理中,代理也拒绝连接。希望我没有疯掉 :)


我没有看到你描述的行为。使用你的代码块(并更改url),我的访问日志显示:[11 / Apr / 2013:18:35:05 +0000]“GET / HTTP / 1.1”200 17353“ - ”“ Mozilla / 5.0(Windows NT 5.1; rv:19.0)Gecko / 20100101 Firefox / 19.0”“ - ”。此请求也通过https使用OS X。 - Jason Nichols
@JasonNichols 我正在运行Windows XP,使用Java 1.7.0_17。 - user845279
使用HttpClient怎么样? - Vitaly
@Vitaly 我正在尝试使用NetBeans测试Web服务连接,但遇到了这个问题。使用HttpClient将是最后的选择。 - user845279
刚在Java 7上尝试了一下,成功设置了头部并提取了内容。响应 - <!doctype html><html itemscope="itemscope" itemtype= ... - Deepak Bala
2个回答

14
通过使用WireShark检查http通信,我已经发现/验证了问题。但没有任何方法可以解决这个问题。通过SSL套接字的通信在加密协议的隐藏下完全无法被轻易地观察到。您可以使用数据包捕获软件查看SSL连接的启动和加密包的交换,但这些包的内容只能在连接的另一端(服务器)上提取。如果不是这样的话,那么整个HTTPS协议将是破碎的,因为它的全部意义就是保护HTTP通信免受中间人类型的攻击(在本例中,MITM是数据包嗅探器)。理论上,唯一知道您的User-Agent标头是否被排除的方法是如果您有访问Google服务器的权限,但实际上,在HTTPS规范或Java的实现中都没有排除通常通过HTTP发送的标头的内容。
URL url = new URL(target);
URLConnection conn = url.openConnection();
conn.setRequestProperty("User-Agent",
        "Mozilla/5.0 (Windows NT 5.1; rv:19.0) Gecko/20100101 Firefox/19.0");
conn.connect();
BufferedReader serverResponse = new BufferedReader(
        new InputStreamReader(conn.getInputStream()));
System.out.println(serverResponse.readLine());
serverResponse.close();

除了HTTPS的目标是"https://www.google.com",HTTP的目标则是"http://www.google.com"。


编辑1:

根据您更新的问题,使用-Dhttp.agent属性确实将'Java/version'附加到用户代理标头中,如以下文档所述:

http.agent (default: “Java/<version>”)
定义在http请求中发送的User-Agent请求标头中的字符串。请注意,“Java/<version>”字符串将附加到属性中提供的字符串中(例如,如果使用了-Dhttp.agent =“foobar”,则用户代理标头将包含“foobar Java/1.5.0”,如果VM的版本为1.5.0)。此属性仅在启动时检查一次。

有问题的代码位于sun.net.www.protocol.http.HttpURLConnection的静态块初始化程序中:

static {
    // ...
    String agent = java.security.AccessController
            .doPrivileged(new sun.security.action.GetPropertyAction(
                    "http.agent"));
    if (agent == null) {
        agent = "Java/" + version;
    } else {
        agent = agent + " Java/" + version;
    }
    userAgent = agent;

    // ...
}

绕过这个“问题”的一种下流方法是使用这段代码片段,但我1000%建议不要使用:

protected void forceAgentHeader(final String header) throws Exception {
    final Class<?> clazz = Class
            .forName("sun.net.www.protocol.http.HttpURLConnection");

    final Field field = clazz.getField("userAgent");
    field.setAccessible(true);
    Field modifiersField = Field.class.getDeclaredField("modifiers");
    modifiersField.setAccessible(true);
    modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
    field.set(null, header);
}

当使用https.proxyHosthttps.proxyPorthttp.agent时,使用此覆盖可以得到期望的结果:

CONNECT www.google.com:443 HTTP/1.1
User-Agent: Mozilla/5.0 (Windows NT 5.1; rv:19.0) Gecko/20100101 Firefox/19.0
Host: www.google.com
Accept: text/html, image/gif, image/jpeg, *; q=.2, /; q=.2
Proxy-Connection: keep-alive

但是,最好还是使用Apache HttpComponents,这更安全:

final DefaultHttpClient client = new DefaultHttpClient();
HttpHost proxy = new HttpHost("127.0.0.1", 8888, "http");
HttpHost target = new HttpHost("www.google.com", 443, "https");
client.getParams().setParameter(ConnRoutePNames.DEFAULT_PROXY, proxy);
HttpProtocolParams
        .setUserAgent(client.getParams(),
                "Mozilla/5.0 (Windows NT 5.1; rv:19.0) Gecko/20100101 Firefox/19.0");
final HttpGet get = new HttpGet("/");

HttpResponse response = client.execute(target, get);

谢谢提供信息,但我想我误导了你,很抱歉。你关于无法看到SSL头的评论让我再次审视了这个问题。我已经更新了我的问题。 - user845279
@user845279 - 好的,你现在的问题更清晰了,有了额外的信息。我在我的答案中添加了一些关于为什么会发生这种情况的信息。 - Perception
太棒了。你的回答已经足够好了,但我希望你能再帮我一件事。为什么我不能通过 URLConnection.setRequestProperty() 设置值?你能为我提供一个解决方法吗?谢谢。 - user845279
@user845279 - setRequestProperty可以使用,但是Sun安全套接字实现在其代码中显式覆盖了User-Agent头,将其设置为-Dhttp.agent + Java/<version>附加的值。我仍在跟踪代码,但看起来只有在使用代理时才会这样做。不幸的是,没有直接的解决方法。 - Perception
1
感谢您的所有帮助。我已向Oracle提交了一个错误报告。[错误ID:9001759](http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=9001759) - user845279

0
我通过使用WireShark检查HTTP通信来找到/验证了问题。有没有什么解决方法?
这里没有问题。无论请求是通过HTTP / HTTPS传输,User-Agent标头都已设置。即使将其设置为像blah blah这样不合理的内容,也可以在HTTPS上工作。下面显示的标头是在底层协议使用HTTPS时捕获的请求标头。
User-Agent: Mozilla/5.0 (Windows NT 5.1; rv:19.0) Gecko/20100101 Firefox/19.0
Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2
Connection: keep-alive

User-Agent: blah blah
Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2
Connection: keep-alive

这是触发请求的代码。
        // localhost:52999 is a reverse proxy to xxx:443
        java.net.URL url = new java.net.URL( "https://localhost:52999/" );
        java.net.URLConnection conn = url.openConnection();
        conn.setRequestProperty("User-Agent","Mozilla/5.0 (Windows NT 5.1; rv:19.0) Gecko/20100101 Firefox/19.0");
        conn.connect();
        java.io.BufferedReader serverResponse = new java.io.BufferedReader(new java.io.InputStreamReader(conn.getInputStream()));
        System.out.println(serverResponse.readLine());
        serverResponse.close();

通常情况下,HTTPS请求是无法被嗅探的(就像@Perception所提到的那样)。将请求通过代理传输,并用自己的虚假CA替换根CA,可以让您查看流量。更简单的方法是只需查看目标服务器的访问日志。但正如您从上面的HTTPS请求片段中看到的那样,发送的“User-Agent”头是正确的。

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