Java JVM中包含“@”的代理密码

Java JVM中包含“@”的代理密码,java,scala,proxy,jvm,Java,Scala,Proxy,Jvm,我有一些Scala代码,它成功地协商(NTLM)代理并访问互联网,指定用户名和密码如下: // Based upon http://rolandtapken.de/blog/2012-04/java-process-httpproxyuser-and-httpproxypassword Authenticator.setDefault(new Authenticator() { override protected def getPasswordAuthentication: Passwo

我有一些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
  }
})
正在运行此程序的环境位于Linux机箱上的Spark群集(使用
Spark submit
运行)。该代理为公司NTLM代理

如果我使用不包含
@
的已知用户名和密码组合,则此操作有效。如果我将其更改为包含
@
的,则它将失败

我曾尝试在
urlEncode
函数中创建
val res=str
(以防它不需要URL编码),尝试使用
\\@
(使用和不使用URL编码)和
^@
(使用和不使用URL编码)。每次我都会遇到一个异常,
无法通过代理进行隧道。代理返回“需要HTTP/1.1 407代理授权”

我知道用户名和密码是有效的,因为它们当前在curl等成功使用的
https\u proxy
变量中设置


因此,除非代理是在运行的Spark服务器中设置的这一事实以某种方式影响了它发生的情况,否则在我看来JVM库不支持代理的身份验证器中有
@

问题不在
java.net
库代码中(编辑:当然对于HTTP基本代理,我还没有能够测试NTLM代理。)
java.net
代码可以使用带有“@”的密码很好地连接。我在下面放了演示代码,允许您验证这一说法

您不需要转义传递到
java.net.PasswordAuthentication
的字符串值,您应该在那里以明文形式传递密码。
java.net
库代码将在通过网络将密码发送到代理时根据需要对其进行编码(请参阅下面的演示代码以验证此声明)

我相信,您的问题一定是在问题中包含的代码之外配置系统的方式

例如,您是否将代理主机名传递给JVM或附近的系统,使其被“@”符号混淆

你能提供更多的背景吗

验证java.net库代码是否可以处理密码中的“@”的演示代码 此代码包括有关在本地计算机上将Fiddler2设置为HTTP代理、将Fiddler2配置为需要密码以及使用java.net库类通过该代理进行连接的说明

代码按原样为我成功运行,如果我将“password”变量更改为不正确的密码,代码将失败

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's Web Sessions list:
        //    prefs set fiddler.proxy.creds dXNlcm5hbWU6cEBzc3dvcmQ=
        //
        // This sets Fiddler to require auth with "username", "p@ssword"
        //
        // See https://stackoverflow.com/questions/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 = "p@ssword";

        System.out.println(
            "prefs set fiddler.proxy.creds " +
            Base64.getEncoder().encodeToString("username:p@ssword".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上,并且将密码设置为命令行参数,则需要使用DOS转义字符“^”,即


如果使用其他机制向Java提供密码,则可能需要不同的转义方案。

这都是在Linux上,而不是在Windows上(因此我尝试使用反斜杠转义)。具体而言,这是我提交的Spark作业的一部分,并使用
--driver Java options='-Dhttps.proxyPassword指定此系统属性="p@ssword“..”
。我只是尝试了一个插入符号,以防万一,但没有骰子。感谢您的澄清。我很确定您的问题是如何在字符串通过各种参数层时转义此字符串,实际上与HTTP代理没有任何关系。您能记录“proxyPassword”的值吗“应用程序启动时的系统属性,以a)确认此假设和b)允许您更快地尝试不同的转义方案,并获得更清晰的反馈?我已经这样做了,并且它在进入
密码验证之前打印出来时看起来是正确的。问题不在于您发布的代码中。我已经更新了我的答案。祝你好运好的,我已经设法修改了您的解决方案,使之与我的环境相匹配(公司代理、我试图运行它的同一个盒子等等),并且它仍然可以工作,因此这一定与通过Spark服务器有关。您可以捕获失败的Java客户端和代理之间以及成功的客户端和代理之间的网络流量,并将两者进行比较。这将很难分析,因为NTLM是一个挑战-响应协议,但在理论上它可能允许您缩小问题的范围。我已经开始这样做,但流量是一个艰难的过程。
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's Web Sessions list:
        //    prefs set fiddler.proxy.creds dXNlcm5hbWU6cEBzc3dvcmQ=
        //
        // This sets Fiddler to require auth with "username", "p@ssword"
        //
        // See https://stackoverflow.com/questions/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 = "p@ssword";

        System.out.println(
            "prefs set fiddler.proxy.creds " +
            Base64.getEncoder().encodeToString("username:p@ssword".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);
        }
    }
}
java -Dhttp.proxyPassword=foo^@bar -jar myapp.jar