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

2022-09-04 22:53:14

我有一些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编码它(即替换为)甚至HTML编码(和)无济于事。@\\\@%40&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
  }

})

运行此操作的环境位于 Linux 机箱上的 Spark 群集(运行方式 )中。代理是公司NTLM代理。spark-submit

如果我使用不包含的已知用户名和密码组合,那么这有效。如果我将其更改为包含 的一个,那么它将失败。@@

我尝试过在函数内部制作(以防万一它不需要URL编码),尝试(有和没有URL编码)和(有和没有URL编码)。每次我得到一个例外。val res = strurlEncode\\@^@Unable to tunnel through proxy. Proxy returns "HTTP/1.1 407 Proxy Authorization Required"

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

因此,除非在正在运行的Spark服务器中设置代理的事实以某种方式影响了它所发生的事情,否则在我看来,JVM库不支持在代理的身份验证器中(至少)。@


答案 1

问题不在于库代码(编辑:当然对于HTTP Basic代理,我还没有能够测试NTLM代理)。该代码可以使用其中带有“@”的密码进行正常连接。我在下面放置了演示代码,允许您验证此声明。java.netjava.net

您不需要转义传入的字符串值,您应该在那里以明文形式传递密码。当通过网络将密码发送到代理时,库代码将根据需要对密码进行编码(请参阅下面的演示代码以验证此声明)。java.net.PasswordAuthenticationjava.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 -Dhttp.proxyPassword=foo^@bar -jar myapp.jar

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


答案 2

推荐