java - 통신 - 자바 http 서버 post




Java의 프록시를 통해 HTTPS 요청을 보내는 방법은 무엇입니까? (2)

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 / 웹 관련 많은 경험이 없습니다. 나는 내가하려고하는 것을 이해하는 데 필요한 모든 세부 사항을 제공했다고 믿는다. 그렇지 않은 경우 알려주십시오.

최신 정보:

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로 수행 한 작업을 재정의합니다. 아직도 그게 필요하지 않니?

또 다른 업데이트 :

모든 것을 신뢰하는 TrustManager를 사용하는 SSLSocketFactory를 사용하도록 SSLTunnelSocketFactory 클래스를 수정했습니다. 이것이 어떤 차이를 만든 것으로 보이지 않습니다. 이것은 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 : Socket [addr = / proxyHost, port = proxyPort, localport = 24372]]" .

이것은 나에게는 의미가 없지만, 이후에 문제가 발생할 수 있습니다.

result.startHandshake ()가 호출되면 Socket이 null이고 result.startHandshake ()가 돌아올 때를 제외하고 동일한 인수로 HttpsClient.afterConnect 호출 스택에 따라 동일한 createSocket 메서드가 다시 호출됩니다. 다시, 결과는 동일한 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 프록시 포트에 연결하면 오류가 발생합니다.

프록시 CONNECT 명령을 사용하여 SSL 터널링 (많은 사람들이 해당 프록시라고 함)을 사용하여 프록시를 통해 연결할 수 있습니다. 그러나 Java는 최신 버전의 프록시 터널링을 지원하지 않습니다. 이 경우 스스로 터널링을 처리해야합니다. 샘플 코드는 여기에서 찾을 수 있습니다.

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를 사용하여 몇 년 전에 작성한 프로그램을 시도했지만 작동하지 않습니다. 분명히 썬은 Java 5에서 언젠가 새로운 버그를 발표했습니다.이 버그 보고서를 보면,

http://bugs.sun.com/view_bug.do?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는 호스트 이름이 cert와 일치하지 않을 때만 관련됩니다. "www.verisign.net"을 사용하여이 작업을 시작했습니다.


직접 실행하는 대신 Apache Commons HttpClient 라이브러리를 사용해보십시오. http://hc.apache.org/httpclient-3.x/index.html

샘플 코드에서 :

  HttpClient httpclient = new HttpClient();
  httpclient.getHostConfiguration().setProxy("myproxyhost", 8080);

  /* Optional if authentication is required.
  httpclient.getState().setProxyCredentials("my-proxy-realm", " myproxyhost",
   new UsernamePasswordCredentials("my-proxy-username", "my-proxy-password"));
  */

  PostMethod post = new PostMethod("https://someurl");
  NameValuePair[] data = {
     new NameValuePair("user", "joe"),
     new NameValuePair("password", "bloggs")
  };
  post.setRequestBody(data);
  // execute method and handle any error responses.
  // ...
  InputStream in = post.getResponseBodyAsStream();
  // handle response.


  /* Example for a GET reqeust
  GetMethod httpget = new GetMethod("https://someurl");
  try { 
    httpclient.executeMethod(httpget);
    System.out.println(httpget.getStatusLine());
  } finally {
    httpget.releaseConnection();
  }
  */






sslhandshakeexception