Java中如何通过代理发送HTTPS请求?

2024-03-04

我正在尝试使用 HttpsUrlConnection 类向服务器发送请求。服务器存在证书问题,因此我设置了一个信任所有内容的 TrustManager,以及一个同样宽松的主机名验证器。当我直接发出请求时,这个管理器工作得很好,但当我通过代理发送请求时,它似乎根本没有被使用。

我这样设置代理设置:

Properties systemProperties = System.getProperties();
systemProperties.setProperty( "http.proxyHost", "proxyserver" );
systemProperties.setProperty( "http.proxyPort", "8080" );
systemProperties.setProperty( "https.proxyHost", "proxyserver" );
systemProperties.setProperty( "https.proxyPort", "8080" );

默认 SSLSocketFactory 的 TrustManager 设置如下:

SSLContext sslContext = SSLContext.getInstance( "SSL" );

// set up a TrustManager that trusts everything
sslContext.init( null, new TrustManager[]
    {
        new X509TrustManager()
        {
            public X509Certificate[] getAcceptedIssuers()
            {
                return null;
            }

            public void checkClientTrusted( X509Certificate[] certs, String authType )
            {
                // everything is trusted
            }

            public void checkServerTrusted( X509Certificate[] certs, String authType )
            {
                // everything is trusted
            }
        }
    }, new SecureRandom() );

// this doesn't seem to apply to connections through a proxy
HttpsURLConnection.setDefaultSSLSocketFactory( sslContext.getSocketFactory() );

// setup a hostname verifier that verifies everything
HttpsURLConnection.setDefaultHostnameVerifier( new HostnameVerifier()
{
    public boolean verify( String arg0, SSLSession arg1 )
    {
        return true;
    }
} );

如果我运行以下代码,最终会出现 SSLHandshakException(“握手期间远程主机关闭连接”):

URL url = new URL( "https://someurl" );

HttpsURLConnection connection = (HttpsURLConnection)url.openConnection();
connection.setDoOutput( true );

connection.setRequestMethod( "POST" );
connection.setRequestProperty( "Content-Type", "application/x-www-form-urlencoded" );
connection.setRequestProperty( "Content-Length", "0" );

connection.connect();

我假设我在处理 SSL 时缺少某种与使用代理有关的设置。如果我不使用代理,我的 checkServerTrusted 方法将被调用;当我通过代理时,这也是我需要发生的事情。

我通常不与 Java 打交道,并且对 HTTP/web 内容也没有太多经验。我相信我已经提供了理解我正在尝试做的事情所需的所有细节。如果情况并非如此,请告诉我。

Update:

阅读了ZZ Coder建议的文章后,我对连接代码做了如下修改:

HttpsURLConnection connection = (HttpsURLConnection)url.openConnection();
connection.setSSLSocketFactory( new SSLTunnelSocketFactory( proxyHost, proxyPort ) );

connection.setDoOutput( true );
connection.setRequestMethod( "POST" );
connection.setRequestProperty( "Content-Type", "application/x-www-form-urlencoded" );
connection.setRequestProperty( "Content-Length", "0" );

connection.connect();

结果(SSLHandshakeException)是相同的。当我将这里的 SLLSocketFactory 设置为 SSLTunnelSocketFactory(文章中解释的类)时,我使用 TrustManager 和 SSLContext 所做的事情被覆盖。我还不需要那个吗?

另一个更新:

我修改了 SSLTunnelSocketFactory 类以使用 SSLSocketFactory,该 SSLSocketFactory 使用信任所有内容的 TrustManager。这似乎没有产生任何影响。这是 SSLTunnelSocketFactory 的 createSocket 方法:

public Socket createSocket( Socket s, String host, int port, boolean autoClose )
    throws IOException, UnknownHostException
{
    Socket tunnel = new Socket( tunnelHost, tunnelPort );

    doTunnelHandshake( tunnel, host, port );

    SSLSocket result = (SSLSocket)dfactory.createSocket(
        tunnel, host, port, autoClose );

    result.addHandshakeCompletedListener(
        new HandshakeCompletedListener()
        {
            public void handshakeCompleted( HandshakeCompletedEvent event )
            {
                System.out.println( "Handshake finished!" );
                System.out.println(
                    "\t CipherSuite:" + event.getCipherSuite() );
                System.out.println(
                    "\t SessionId " + event.getSession() );
                System.out.println(
                    "\t PeerHost " + event.getSession().getPeerHost() );
            }
        } );

    result.startHandshake();

    return result;
}

当我的代码调用connection.connect时,就会调用这个方法,并且调用doTunnelHandshake成功。下一行代码使用我的 SSLSocketFactory 创建 SSLSocket;此调用后结果的 toString 值为:

“1d49247[SSL_NULL_WITH_NULL_NULL:套接字[addr=/proxyHost,端口=proxyPort,本地端口=24372]]”.

这对我来说毫无意义,但这可能是此后事情崩溃的原因。

当调用 result.startHandshake() 时,根据调用堆栈 HttpsClient.afterConnect,使用相同的参数再次调用相同的 createSocket 方法,但 Socket s 为 null,并且当它转到 result.startHandshake() 时再次,结果是相同的 SSLHandshakeException。

对于这个日益复杂的谜题,我是否仍然缺少重要的一块?

这是堆栈跟踪:



javax.net.ssl.SSLHandshakeException: Remote host closed connection during handshake
  at com.sun.net.ssl.internal.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:808)
  at com.sun.net.ssl.internal.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1112)
  at com.sun.net.ssl.internal.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1139)
  at com.sun.net.ssl.internal.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1123)
  at gsauthentication.SSLTunnelSocketFactory.createSocket(SSLTunnelSocketFactory.java:106)
  at sun.net.www.protocol.https.HttpsClient.afterConnect(HttpsClient.java:391)
  at sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect(AbstractDelegateHttpsURLConnection.java:166)
  at sun.net.www.protocol.https.HttpsURLConnectionImpl.connect(HttpsURLConnectionImpl.java:133)
  at gsauthentication.GSAuthentication.main(GSAuthentication.java:52)
Caused by: java.io.EOFException: SSL peer shut down incorrectly
  at com.sun.net.ssl.internal.ssl.InputRecord.read(InputRecord.java:333)
  at com.sun.net.ssl.internal.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:789)
  ... 8 more
  

HTTPS 代理没有意义,因为出于安全原因您无法在代理处终止 HTTP 连接。根据您的信任策略,如果代理服务器具有 HTTPS 端口,它可能会起作用。您的错误是由于使用 HTTPS 连接到 HTTP 代理端口而导致的。

您可以使用 proxy CONNECT 命令通过使用 SSL 隧道的代理(许多人称之为代理)进行连接。但是,Java 不支持较新版本的代理隧道。在这种情况下,您需要自己处理隧道。您可以在这里找到示例代码,

http://www.javaworld.com/javaworld/javatips/jw-javatip111.html http://www.javaworld.com/javaworld/javatips/jw-javatip111.html

编辑:如果您想击败 JSSE 中的所有安全措施,您仍然需要自己的 TrustManager。像这样的事情,

 public SSLTunnelSocketFactory(String proxyhost, String proxyport){
      tunnelHost = proxyhost;
      tunnelPort = Integer.parseInt(proxyport);
      dfactory = (SSLSocketFactory)sslContext.getSocketFactory();
 }
 
 ...

 connection.setSSLSocketFactory( new SSLTunnelSocketFactory( proxyHost, proxyPort ) );
 connection.setDefaultHostnameVerifier( new HostnameVerifier()
 {
    public boolean verify( String arg0, SSLSession arg1 )
    {
        return true;
    }
 }  );

编辑2:我刚刚尝试了几年前使用 SSLTunnelSocketFactory 编写的程序,但它也不起作用。显然,Sun 在 Java 5 中的某个时候引入了一个新错误。请参阅此错误报告,

https://bugs.java.com/bugdatabase/view_bug?bug_id=6614957 https://bugs.java.com/bugdatabase/view_bug?bug_id=6614957

好消息是 SSL 隧道错误已修复,因此您可以仅使用默认工厂。我刚刚尝试使用代理,一切都按预期进行。看我的代码,

public class SSLContextTest {

    public static void main(String[] args) {

        System.setProperty("https.proxyHost", "proxy.xxx.com");
        System.setProperty("https.proxyPort", "8888");

        try {

            SSLContext sslContext = SSLContext.getInstance("SSL");

            // set up a TrustManager that trusts everything
            sslContext.init(null, new TrustManager[] { new X509TrustManager() {
                public X509Certificate[] getAcceptedIssuers() {
                    System.out.println("getAcceptedIssuers =============");
                    return null;
                }

                public void checkClientTrusted(X509Certificate[] certs,
                        String authType) {
                    System.out.println("checkClientTrusted =============");
                }

                public void checkServerTrusted(X509Certificate[] certs,
                        String authType) {
                    System.out.println("checkServerTrusted =============");
                }
            } }, new SecureRandom());

            HttpsURLConnection.setDefaultSSLSocketFactory(
                    sslContext.getSocketFactory());

            HttpsURLConnection
                    .setDefaultHostnameVerifier(new HostnameVerifier() {
                        public boolean verify(String arg0, SSLSession arg1) {
                            System.out.println("hostnameVerifier =============");
                            return true;
                        }
                    });
            
            URL url = new URL("https://www.verisign.net");
            URLConnection conn = url.openConnection();
            BufferedReader reader = 
                new BufferedReader(new InputStreamReader(conn.getInputStream()));
            String line;
            while ((line = reader.readLine()) != null) {
                System.out.println(line);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } 
    }
}

这是我运行程序时得到的结果

checkServerTrusted =============
hostnameVerifier =============
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
......

正如您所看到的,SSLContext 和 hostnameVerifier 都被调用。仅当主机名与证书不匹配时才涉及 HostnameVerifier。我使用“www.verisign.net”来触发此操作。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Java中如何通过代理发送HTTPS请求? 的相关文章

  • 从 BroadcastReceiver 获取方法来更新 UI

    我正在尝试根据变量的变化更新用户界面BroadcastReceiver 因此 我需要调用一个扩展类的方法 以获取我提到的变量 BroadcastReceiver in MainActivity取决于但我无法以任何方式获得真正的返回值 扩展的
  • 在此代码中,Runnable 未实例化。为什么?

    Runnable cannot instantiate public class Thread4 public static void main String args Thread t1 new Thread new Runnable R
  • ListView:防止视图回收

    我有一个使用回收视图的 ListView 我试图阻止视图被回收 所以我使用 setHasTransientState android support v4 view ViewCompatJB setHasTransientState Vie
  • HttpSession 内的同步是否可行?

    UPDATE 问题后立即解决 问题 通常 同步是在 JVM 内序列化并行请求 例如 private static final Object LOCK new Object public void doSomething synchroniz
  • Java:无安全管理器:RMI 类加载器已禁用

    您好 我有 RMI 应用程序 现在我尝试从客户端调用服务器上的一些方法 我有以下代码 public static void main final String args try Setting the security manager Sy
  • 如何停止使用扫描仪从标准输入读取多行?

    我正在做一个 JAVA 作业 应该处理多行输入 指令显示 输入是从标准输入读取的 给出了示例输入的示例 one 1 two 2 three 3 我不明白上面的示例输入 从标准输入读取 是什么意思 这是我编写的一个测试程序 它可以消除我的困惑
  • 为什么Java中的文件名与公共类名相同? [关闭]

    Closed 这个问题是基于意见的 help closed questions 目前不接受答案 在Java中 文件名应该与文件名相同public class包含在该文件中 为什么这是一个限制 它有什么目的 Java 有一个有趣的方法 如果给
  • Java - toString 到 Color

    我一整天都在努力解决这个问题 基本上我做了一个 for 循环 将条目添加到数组列表中 其中一项是 颜色 变量 我已经用过random nextInt为颜色构造函数的红色 绿色和蓝色部分创建新值 我还设置了一个toString方法 这样我就可
  • 如何修复maven错误JAVA_HOME环境变量未正确定义

    当我在虚拟环境中检查maven的版本时 出现以下错误 The JAVA HOME environment variable is not defined correctly This environment variable is need
  • java 中的 Try-with-resources 和 return 语句

    我想知道是否放一个return里面的声明尝试资源block 防止资源自动关闭 try Connection conn return conn createStatement execute 如果我写这样的东西将会联系被关闭 Oracle 文
  • 开发人员实际上是否使用 vim 在 Windows 操作系统上编写代码(Java)? [关闭]

    就目前情况而言 这个问题不太适合我们的问答形式 我们希望答案得到事实 参考资料或专业知识的支持 但这个问题可能会引发辩论 争论 民意调查或扩展讨论 如果您觉得这个问题可以改进并可能重新开放 访问帮助中心 help reopen questi
  • JPA 的 Hibernate 查询提示

    我一直在尝试为所有可以通过设置的提示找到一个明确的资源Query setHint String Object JPA 中的方法调用 但我一无所获 有人知道一个好的参考吗 See 3 4 1 7 查询提示 http docs jboss or
  • Java 常量枚举[重复]

    这个问题在这里已经有答案了 可能的重复 理解 Java 中的枚举 https stackoverflow com questions 1419835 understanding enums in java 为什么我们应该使用枚举而不是 Ja
  • 用于安装 R 软件包的备用编译器:clang:错误:不支持的选项“-fopenmp”

    我正在尝试在 OS X 10 11 6 上使用 R 版本 3 4 0 安装 rJava 包 install packages rJava type source 我收到以下错误 clang o libjri jnilib Rengine o
  • 线程上下文类加载器和普通类加载器的区别

    线程的上下文类加载器和普通类加载器有什么区别 也就是说 如果Thread currentThread getContextClassLoader and getClass getClassLoader 返回不同的类加载器对象 将使用哪一个
  • 测量 tomcat 的排队请求数

    因此 使用tomcat 您可以设置acceptCount值 默认为100 这意味着当所有工作线程都忙时 新连接被放置在队列中 直到队列满 之后它们被拒绝 我想要的是监视此队列中项目的大小 但无法确定是否有办法通过 JMX 获取此值 即不是队
  • 一个类中有多个具有相同参数类型的方法

    我知道 至少已经有了关于这个主题的一个问题 https stackoverflow com questions 5561436 can two java methods have same name with different retur
  • 通过向上转换将 Java.sql.date 转换为 Java.util.date 安全吗?

    java sql date 扩展了 java util date 那么通过将 java sql date 转换为 java util date 是否可以在两者之间进行转换 或者有其他方法可以转换它们吗 您不一定需要强制转换 您可以将 SQL
  • AES 密钥是随机的吗?

    AES 密钥可以通过此代码生成 KeyGenerator kgen KeyGenerator getInstance AES kgen init 128 but 如果我有一个 非常可靠 的生成随机数的方法 我可以这样使用它吗 SecureR
  • 膨胀类 android.support.design.widget.CoordinatorLayoute 时出错

    我正在尝试运行我的应用程序 但不断收到标题中列出的错误 我读过周围的内容 人们说尝试将主题更改为 AppCombat 主题 但这似乎不起作用 以下是我遇到的错误 Process com example jmeyer27 crazytiles

随机推荐