Установить свойство user-agent в заголовке соединения https

Я не могу правильно установить свойство user-agent для подключения https. Из того, что я собрал, свойства http-заголовка могут быть заданы либо с помощью опции -Dhttp.agent VM, либо через URLConnection.setRequestProperty(). Однако установка пользовательского агента через опцию VM приводит к тому, что "Java/[версия]" добавляется к любому значению 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();

Я нашел/подтвердил эту проблему, проверив http-протоколы с помощью WireShark. Есть ли способ обойти это?

Обновление: дополнительная информация

Кажется, я не выглядел достаточно глубоко в сообщении. Код запускается из-за прокси-сервера, поэтому наблюдаемое сообщение противоречит прокси-серверу, установленному через -Dhttps.proxyHost, а не целевому веб-сайту (google.com). Во всяком случае, во время соединения https метод CONNECT, а не GET. Вот проводка захвата https-связи. Как я уже упоминал выше, пользовательский агент устанавливается через -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-соединения, и прокси также отказывается от соединения. Надеюсь, я не схожу с ума:).

Ответ 1

Я нашел/подтвердил эту проблему, проверив http-протоколы с помощью WireShark. Есть ли какой-либо путь вокруг этого

Это невозможно. Связь через сокет SSL полностью скрывается от случайного наблюдения по протоколу шифрования. Используя программное обеспечение для захвата пакетов, вы сможете просмотреть инициирование SSL-соединения и обмен зашифрованными пакетами, но содержимое этих пакетов можно извлечь только на другом конце соединения (сервер). Если бы это было не так, то протокол HTTPS в целом был бы сломан, так как в целом он должен защищать HTTP-связь от атак типа "человек в середине" (где в этом случае MITM является сниффером пакетов).

Пример. Захват запроса HTTPS (частичный):

.n.... E............../.. 5..3..9..2..8................ @........................ Ql. {... б.... OSR..!. 4. $. Т...,.. T.... Q... M..Ql. {... LM..L... um.M........... s.... п... р ^ 0}.. I..G4.HK.n...... 8Y............... E... A.. > ... 0... 0.........).s....... 0..*.ЧАС....... 0F1.0... U.... US1.0... U., Google Inc1 "0..U.... Google Internet Authority0.. 130327132822Z. 131231155850Z0h1.0... U.... US1.0... U... California1.0... U... Mountain View1.0... U., Google Inc1.0... U.... www.google.com0..0

Теоретически единственный способ узнать, действительно ли исключен ваш заголовок User-Agent, - это доступ к серверам Google, но на самом деле нет ничего ни в спецификации HTTPS, ни в реализации Java, которая исключает заголовки, которые обычно отправляется через HTTP.

Пример Захват HTTP-запроса:

GET/HTTP/1.1
User-Agent: Mozilla/5.0 (Windows NT 5.1; rv: 19.0) Gecko/20100101 Firefox/19.0
Хост: www.google.com
Accept: text/html, image/gif, image/jpeg, *; q =.2,/; д = 0,2
Соединение: keep-alive

Оба примера захвата были сгенерированы с помощью точного того же кода:

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(по умолчанию: "Java/<version> " )
Определяет строку, отправленную в заголовке запроса User-Agent в http-запросах. Обратите внимание, что строка "Java/<version> " будет добавлено к объекту, указанному в свойстве (например, если используется -Dhttp.agent = "foobar", заголовок User-Agent будет содержать "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.proxyHost, https.proxyPort и http.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
Хост: www.google.com
Accept: text/html, image/gif, image/jpeg, *; q =.2,/; д = 0,2
Прокси-соединение: сохранить-жить

Но да, не делай этого. Его гораздо безопаснее использовать 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);

Ответ 2

Я нашел/подтвердил эту проблему, проверив http-протоколы с помощью WireShark. Есть ли способ обойти это?

Здесь нет проблем. В заголовке User-Agent задается, транспортируется ли запрос через HTTP/HTTPS. Даже установка его на что-то необоснованное, например, blah blah работает на HTTPS. Заголовки, показанные ниже, были захвачены, когда используемый базовый протокол был 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). Сопровождение запроса через прокси-сервер, заменяющий корневой ЦС собственным фальшивым ЦС, позволит вам увидеть трафик. Более простой способ - просто просмотреть журнал доступа целевого сервера. Но, как видно из фрагмента запроса HTTPS выше, заголовок User-Agent, который отправляется, является правильным.