API песочницы Paypal: javax.net.ssl.SSLHandshakeException: получено фатальное предупреждение: handshake_failure

Я получаю исключение при использовании PayPal MassPay API в режиме песочницы.

Тот же самый код работал раньше, теперь он дает мне SSLHandshakeException. Я думаю, что это связано с последними обновлениями SSL-сертификации PayPal. Может ли кто-нибудь помочь мне решить эту проблему?

Ниже приведен мой журнал исключений:

http-bio-9090-exec-1, READ: TLSv1 Alert, length = 2
http-bio-9090-exec-1, RECV TLSv1 ALERT:  fatal, handshake_failure
http-bio-9090-exec-1, called closeSocket()
http-bio-9090-exec-1, handling exception: javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure
com.paypal.core.rest.PayPalRESTException: Received fatal alert: handshake_failure
    at com.paypal.core.rest.PayPalResource.execute(PayPalResource.java:374)
    at com.paypal.core.rest.PayPalResource.configureAndExecute(PayPalResource.java:225)
    at com.paypal.sdk.openidconnect.Tokeninfo.createFromAuthorizationCode(Tokeninfo.java:245)
    at com.oldwallet.controllers.CouponPaymentController.redeemed(CouponPaymentController.java:321)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:606)
    at org.springframework.web.method.support.InvocableHandlerMethod.invoke(InvocableHandlerMethod.java:215)
    at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:132)
    at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:104)
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandleMethod(RequestMappingHandlerAdapter.java:745)
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:686)
    at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:80)
    at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:925)
    at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:856)
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:953)
    at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:844)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:621)
    at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:829)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:728)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:305)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:222)
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:123)
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:472)
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:171)
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:99)
    at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:953)
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:118)
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:408)
    at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1008)
    at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:589)
    at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:310)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
    at java.lang.Thread.run(Thread.java:745)
Caused by: javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure
    at sun.security.ssl.Alerts.getSSLException(Alerts.java:192)
    at sun.security.ssl.Alerts.getSSLException(Alerts.java:154)
    at sun.security.ssl.SSLSocketImpl.recvAlert(SSLSocketImpl.java:1959)
    at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:1077)
    at sun.security.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1312)
    at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1339)
    at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1323)
    at sun.net.www.protocol.https.HttpsClient.afterConnect(HttpsClient.java:563)
    at sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect(AbstractDelegateHttpsURLConnection.java:185)
    at sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1300)
    at java.net.HttpURLConnection.getResponseCode(HttpURLConnection.java:468)
    at sun.net.www.protocol.https.HttpsURLConnectionImpl.getResponseCode(HttpsURLConnectionImpl.java:338)
    at com.paypal.core.HttpConnection.execute(HttpConnection.java:93)
    at com.paypal.core.rest.PayPalResource.execute(PayPalResource.java:367)
    ... 36 more

Ответы

Ответ 1

Проблема в том, что PayPal недавно (19 или 20 января) переключил песочницу на TLS 1.2 и больше не поддерживает TLS 1. Я предполагаю, что вы работаете на Java SE 7 или старше. Если вы немного поискаете сеть, вы найдете следующее:

Хотя SunJSSE в выпуске Java SE 7 поддерживает TLS 1.1 и TLS 1.2, ни одна версия не включена по умолчанию для клиентских подключений. Некоторые серверы не обеспечивают правильную совместимость отказываются разговаривать с клиентами TLS 1.1 или TLS 1.2. Для обеспечения совместимости, SunJSSE не включает TLS 1.1 или TLS 1.2 по умолчанию для клиента соединения.

Ссылка

Чтобы включить TLS 1.2, вы можете использовать следующую настройку виртуальной машины: -Dhttps.protocols=TLSv1.1,TLSv1.2 Затем она будет работать.

Если вы хотите увидеть более подробную информацию о рукопожатии, вы можете использовать эту опцию VM: -Djavax.net.debug=all

Затем вы можете подтвердить, что TLS 1.2 отключен, если вы видите что-то вроде этого:

*** ClientHello, TLSv1

После чтения ответа сервера будет выбрано исключение. Java 8 не имеет такой проблемы, поскольку по умолчанию TLS 1.2 включен.

В настоящее время затронута только песочница. Вы можете прочитать на сайте PayPal:

PayPal обновляет свои услуги, требуя TLS v1.2 для всех HTTPS 17 июня 2016 года. После этой даты все TLS v1.0 и TLS API-интерфейсам v1.1 будет отказано.

Скорее всего, это будет отложено еще больше.

Вы можете воспроизвести проблему и поэкспериментировать с этим простым кодом выброса:

URL sandbox = new URL("https://api.sandbox.paypal.com/v1/oauth2/token");
URLConnection yc = sandbox.openConnection();

BufferedReader in = new BufferedReader(new InputStreamReader(
    yc.getInputStream()));
String inputLine;
while ((inputLine = in.readLine()) != null)
   System.out.println(inputLine);
in.close();

Ответ 2

http-bio-9090-exec-1, RECV TLSv1 ALERT: фатальный, handshake_failure

Похоже, вы используете более старую версию TLS. Недавно PayPal (несколько дней назад) внесла изменения в свою песочницу, требуя, чтобы все соединения выполнялись через HTTP 1.1 и TLS 1.2. Попробуйте установить код для использования более новой версии TLS (1.2) и посмотрите, поможет ли это. По-видимому, это сработало для многих людей здесь.

Ссылка на описание изменения в блоге разработчиков PayPal

https://devblog.paypal.com/upcoming-security-changes-notice/

Ответ 3

На этой неделе я столкнулся с той же проблемой (Paypal TLS 1.2 Vs JRE 1.6). После некоторых сумасшедших часов мне удалось решить проблему с помощью BouncyCastle и некоторого кода, которым я не горжусь (как патч. Долгосрочное решение будет обновляться до версии JRE 1.8).

BouncyCastle зависимость:

<dependency>
    <groupId>org.bouncycastle</groupId>
    <artifactId>bcprov-jdk15on</artifactId>
    <version>1.54</version>
</dependency>

Мой рабочий код:

package es.webtools.eencuesta.redsys.component.impl;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.Socket;
import java.net.URL;
import java.net.URLDecoder;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;

import org.bouncycastle.crypto.tls.CertificateRequest;
import org.bouncycastle.crypto.tls.DefaultTlsClient;
import org.bouncycastle.crypto.tls.TlsAuthentication;
import org.bouncycastle.crypto.tls.TlsClientProtocol;
import org.bouncycastle.crypto.tls.TlsCredentials;

import es.webtools.eencuesta.common.util.HttpUtils;

public class PayPayAPIHandler {

    public static Map<String, String> doAPIRequest(Map<String, String> paramMap) {

    StringBuilder data = new StringBuilder();
    Iterator<Entry<String, String>> paramIt = paramMap.entrySet().iterator();
    while (paramIt.hasNext()) {
        Entry<String, String> param = paramIt.next();
        data.append(param.getKey()).append("=").append(HttpUtils.encodeUTF8(param.getValue()));
        if (paramIt.hasNext()) {
            data.append("&");
        }
    }

    try {
        URL url = new URL("https://api-3t.sandbox.paypal.com/nvp");

        // TODO #39 Utilizar HttpConnection (Java 8 implementa TLS 1.2, necesaria para comunicación con PayPal)
        java.security.SecureRandom secureRandom = new java.security.SecureRandom();
        Socket socket = new Socket(java.net.InetAddress.getByName(url.getHost()), 443);
        TlsClientProtocol protocol = new TlsClientProtocol(socket.getInputStream(), socket.getOutputStream(), secureRandom);
        DefaultTlsClient client = new DefaultTlsClient() {
            public TlsAuthentication getAuthentication() throws IOException {
                TlsAuthentication auth = new TlsAuthentication() {
                    // Capture the server certificate information!
                    public void notifyServerCertificate(org.bouncycastle.crypto.tls.Certificate serverCertificate) throws IOException {
                    }

                    public TlsCredentials getClientCredentials(CertificateRequest certificateRequest) throws IOException {
                        return null;
                    }
                };
                return auth;
            }
        };

        protocol.connect(client);
        java.io.OutputStream output2 = protocol.getOutputStream();
        output2.write(("POST " + url.getPath() + " HTTP/1.1\r\n").getBytes("UTF-8"));
        output2.write(("Host: " + url.getHost() + "\r\n").getBytes("UTF-8"));
        output2.write("Connection: close\r\n".getBytes("UTF-8")); // So the server will close socket immediately.
        output2.write(("Content-Length: " + data.length() + "\r\n").getBytes("UTF-8")); // So the server will close socket immediately.
        output2.write("Content-Type:text/plain; charset=UTF-8\r\n".getBytes("UTF-8")); // So the server will close socket immediately.
        output2.write("\r\n".getBytes("UTF-8")); // HTTP1.1 requirement: last line must be empty line.
        output2.write(data.toString().getBytes("UTF-8"));
        output2.flush();

        InputStream input2 = protocol.getInputStream();
        StringBuilder stringBuffer = new StringBuilder();
        try {
            InputStreamReader reader = new InputStreamReader(input2, "UTF-8");
            int ch;
            while ((ch = reader.read()) > -1) {
                stringBuffer.append((char) ch);
            }
            reader.close();
        } catch (Exception e) {
            // Log some messages...
        }

        Map<String, String> result = new HashMap<String, String>();
        String[] lines = stringBuffer.toString().split("\r\n");
        String paramsLine = "";
        for (int i = 0; i < lines.length; i++) {
            if (lines[i].equals("")) {
                paramsLine = lines[i + 1];
                i = lines.length;
            }
        }

        // El contenido de la respuesta vendrá después del salto de linea
        for (String param : paramsLine.split("&")) {
            String[] keyValue = param.split("=");
            result.put(keyValue[0], URLDecoder.decode(keyValue[1], "UTF-8"));
        }

        return result;
    } catch (Exception e) {
        // Log some messages....
        return null;
    }
}

}

Ответ 4

Если вы вызываете https-запрос из веб-службы или в отдельное приложение в JAVA, используйте следующую строку, чтобы заставить ее работать:

System.setProperty("https.protocols", "TLSv1,TLSv1.1,TLSv1.2");

Ответ 5

Моя проблема заключалась в том, что у меня был установлен java 7, поэтому я обновился до java 8 и voila, исключения больше не было:)