Подтвердить что ты не робот

Пароль прокси, содержащий `@` в JVM

У меня есть код Scala, которому удалось успешно согласовать прокси (NTLM) и получить доступ к Интернету, указав имя пользователя и пароль так:

// Based upon http://rolandtapken.de/blog/2012-04/java-process-httpproxyuser-and-httpproxypassword
Authenticator.setDefault(new Authenticator() {

  override protected def getPasswordAuthentication: PasswordAuthentication = {

    if (getRequestorType eq RequestorType.PROXY) {
      val prot = getRequestingProtocol.toLowerCase

      // This allows us to only return a PasswordAuthentication when we have
      // all four of the host, port, user and password _and_ the host and
      // port match the actual proxy wanting authentication.
      for {
        host <- Option(System.getProperty(prot + ".proxyHost"))
        if getRequestingHost.equalsIgnoreCase(host)
        port <- Try(augmentString(System.getProperty(prot + ".proxyPort")).toInt).toOption
        if port == getRequestingPort
        user <- Option(System.getProperty(prot + ".proxyUser"))
        pass <- Option(System.getProperty(prot + ".proxyPassword"))
      } yield return new PasswordAuthentication(user, pass.toCharArray)
    }

    // One of the if-statements failed.  No authentication for you!
    null
  }
})

Однако теперь мне была предложена новая комбинация имени пользователя и пароля, а пароль содержит @. Я пробовал использовать пароль напрямую, избегая его (с помощью \ и \\ в случае необходимости двухуровневого экранирования), url-encoding it (т.е. заменяя @ на %40) и даже HTML-кодирование (&commat; и &#64;) безрезультатно.

Я знаю, что пароль работает, поскольку он используется в других системах для приложений, отличных от JVM, для доступа к Интернету, устанавливая переменную http_proxy, но она здесь не работает.

Любые идеи?


ИЗМЕНИТЬ

Чтобы попытаться очистить некоторые вещи, я попытался упростить свой код:

Authenticator.setDefault(new Authenticator() {

  def urlEncode(str: String): String = {
    val res = URLEncoder.encode(str, "UTF-8")
    // To confirm it working
    println(s"${str} -> ${res}")
    res
  }

  override protected def getPasswordAuthentication: PasswordAuthentication = {

    if (getRequestorType eq RequestorType.PROXY) {

      return new PasswordAuthentication(urlEncode("username"), urlEncode("[email protected]").toCharArray);
    }
    null
  }

})

Среда, в которой он выполняется, находится в кластере Spark (выполняется с spark-submit) в ящике Linux. Прокси-сервер - это корпоративный NTLM.

Если я использую известную комбинацию имени пользователя и пароля, которая не содержит @, тогда это работает. Если я изменил его на один, содержащий @, тогда он не сработает.

Я попытался сделать val res = str внутри функции urlEncode (в случае, если он не должен быть закодирован в URL), попытался иметь \\@ (с кодировкой URL и без) и ^@ (с и без кодировки URL). Каждый раз я получаю исключение Unable to tunnel through proxy. Proxy returns "HTTP/1.1 407 Proxy Authorization Required".

Я знаю, что имя пользователя и пароль действительны, поскольку они в настоящее время установлены в переменной https_proxy, которая успешно используется curl и т.д.

Поэтому, если тот факт, что прокси-сервер не настроен на работающем сервере Spark, каким-то образом влияет на то, что с ним происходит, мне кажется, что библиотеки JVM не поддерживают наличие @ в аутентификаторе для прокси (по крайней мере).

4b9b3361

Ответ 1

Проблема не входит в код библиотеки java.net (EDIT: конечно, для базовых прокси HTTP, я еще не смог протестировать прокси-серверы NTLM). Код java.net может подключаться отлично, используя пароли с "@" в них. Я привел демо-код ниже, который позволяет вам проверить это требование.

Вам не нужно избегать строкового значения, которое вы передаете в java.net.PasswordAuthentication, вы должны передать свой пароль открытым текстом. Код библиотеки java.net позаботится о необходимости кодирования вашего пароля при отправке по сети через прокси-сервер (см. Демонстрационный код ниже, чтобы проверить это требование).

Я считаю, что ваша проблема должна быть в том, как вы настраиваете свою систему за пределами кода, который вы включили в вопрос до сих пор.

Например, передали ли вы прокси-имя хоста JVM или соседней системе таким образом, что это путано с символом "@" ?

Пожалуйста, можете ли вы предоставить больше контекста?

Демо-код для проверки того, что код библиотеки java.net может справиться с "@" в пароле

Этот код содержит инструкции по настройке Fiddler2 в качестве прокси-сервера HTTP на вашем локальном компьютере, настройке Fiddler2 для запроса пароля и подключении через этот прокси-сервер с использованием классов библиотеки java.net.

Код работает успешно для меня как есть, и не удается, если я изменю переменную "пароль" на неверный пароль.

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.Authenticator;
import java.net.PasswordAuthentication;
import java.net.URL;
import java.util.Base64;

public class q45749081 {

    public static void main(String[] args) throws Exception {

        // Start Fiddler HTTP proxy https://www.telerik.com/download/fiddler
        // Click "rules" -> "require proxy authentication"
        // Type into the "QuickExec" box below Fiddler Web Sessions list:
        //    prefs set fiddler.proxy.creds dXNlcm5hbWU6cEBzc3dvcmQ=
        //
        // This sets Fiddler to require auth with "username", "[email protected]"
        //
        // See https://stackoverflow.com/info/18259969/changing-username-and-password-of-fiddler-proxy-server

        // Note that you must start a new process each time you change the password
        // here, as sun.net.www.protocol.http.HttpURLConnection caches the proxy password
        // for the lifetime of the JVM process
        String password = "[email protected]";

        System.out.println(
            "prefs set fiddler.proxy.creds " +
            Base64.getEncoder().encodeToString("username:[email protected]".getBytes()));

        Authenticator.setDefault(new Authenticator() {
            @Override
            protected PasswordAuthentication getPasswordAuthentication() {
                return new PasswordAuthentication(
                    "username",
                    password.toCharArray());
            }
        });


        System.setProperty("http.proxyHost", "localhost");
        System.setProperty("http.proxyPort", "8888");

        System.out.println("Connecting to Google via authenticated proxy with password '"
            + password + "'");
        try (InputStream conn = new URL("http://www.google.com/").openStream()) {
            try (BufferedReader r = new BufferedReader(new InputStreamReader(conn))) {
                System.out.println(r.readLine());
                System.out.println("OK");
            }
        } catch (Exception e) {
            System.out.println("Failed: " + e);
        }
    }
}

Первый ответ:

Код, который вы указали, берет пароль из свойства системы JVM. Как вы помещаете свой пароль в это свойство? Я подозреваю, что проблема там, а не в коде, который вы показали.

Если вы находитесь в Windows, и если вы задаете пароль в качестве аргумента командной строки, вам нужно будет использовать escape-код DOS char "^", т.е.

java -Dhttp.proxyPassword=foo^@bar -jar myapp.jar

Если вы используете другой механизм для предоставления пароля Java, вам может потребоваться другая схема экранирования.