在JVM中包含`@`符号的代理密码

13

我有一些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编码(即将@替换为%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's working
    println(s"${str} -> ${res}")
    res
  }

  override protected def getPasswordAuthentication: PasswordAuthentication = {

    if (getRequestorType eq RequestorType.PROXY) {

      return new PasswordAuthentication(urlEncode("username"), urlEncode("p@ssword").toCharArray);
    }
    null
  }

})

这个程序运行的环境是在一个Spark集群上(使用 spark-submit )在Linux服务器上。代理是公司的NTLM代理。

如果我使用已知的不含@的用户名和密码组合,则可以正常工作。如果更改为一个包含@的组合,则会失败。

我尝试在urlEncode函数中使val res = str(以防需要进行URL编码),尝试使用 \\ @ (带和不带URL编码)和 ^@ (带和不带URL编码)。每次都会出现异常:无法通过代理进行隧道连接。代理返回“HTTP / 1.1 407代理授权要求”

我知道用户名和密码是有效的,因为它们当前设置在https_proxy变量中,并成功地被curl等使用。

所以,除非设置代理的正在运行的Spark服务器会影响其发生的事情,否则在代理的认证器中具有@的事实似乎表明JVM库不支持代理的认证器中包括@ (至少是这样)。


你可以捕获失败的Java客户端和代理之间以及成功的客户端和代理之间的网络流量,并进行比较。由于NTLM是一种挑战-响应协议,因此分析可能会很困难,但从理论上讲,它可能会帮助你缩小问题范围。 - Rich
我已经开始做了,但是交通非常拥挤,让人感到很痛苦。 - ivanm
1个回答

6
问题不在于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://dev59.com/B3XYa4cB1Zd3GeqP9rxP

        // 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 -Dhttp.proxyPassword=foo^@bar -jar myapp.jar

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


这全部都是关于Linux的,不是Windows(因此我尝试使用反斜杠进行转义)。具体来说,这是我正在提交的Spark作业的一部分,并且我正在使用--driver-java-options='-Dhttps.proxyPassword="p@ssword" ...'指定此系统属性。我刚刚尝试了一个插入符号,但没有成功。 - ivanm
感谢您的澄清。我相当确定您的问题是如何在通过各种参数层时转义此字符串,实际上与HTTP代理没有任何关系。您能否在应用程序启动时记录“proxyPassword”系统属性的值,以确认此假设并允许您更快地尝试不同的转义方案,并获得更清晰的反馈? - Rich
抱歉,我说得太早了;看起来缓存正在进行中,即使我更改了密码(为错误的密码),重新编译和运行它仍然有效;所以我必须等待代理服务器停止自动让我通过,然后再尝试。 - ivanm
是的,我正在尝试避免这种情况,因为我们的管理员可能会很麻烦... 我已经奖励了你的帮助(谢谢!),但不会接受答案,以防有人能够提出实际解决方案。 - ivanm
谢谢。我已经查看了sun.net.www.protocol.http.ntlm中的代码(我假设您正在使用标准的Sun JVM?),在我的看来,JVM代码应该可以很好地处理带有“@”符号的密码。我仍然怀疑您在这里遇到的问题是设置或使用问题,而不是JVM本身存在无法克服的错误,但如果没有可重现的案例进行调试,我也无能为力。我已经提出了https://serverfault.com/questions/871085/looking-for-a-lightweight-ntlm-http-proxy-server - Rich
显示剩余5条评论

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