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
5 ответов
Проблема в том, что 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 года. После этой даты всем соединениям API TLS v1.0 и TLS 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();
http-bio-9090-exec-1, RECV TLSv1 ALERT: смертельно, рукопожатие_отказ
Похоже, вы используете более старую версию TLS. Недавно PayPal (несколько дней назад) внес изменения в свою изолированную программную среду, требуя, чтобы все соединения выполнялись через HTTP 1.1 и TLS 1.2. Попробуйте настроить свой код для использования более новой версии TLS (1.2) и посмотрите, поможет ли это. Видимо, это сработало для многих людей здесь.
Ссылка на описание изменений в блоге разработчиков PayPal
https://devblog.paypal.com/upcoming-security-changes-notice/
Если вы вызываете запрос https из веб-службы или из автономного приложения в JAVA, используйте следующую строку, чтобы он работал:
System.setProperty(“https.protocols”, “TLSv1,TLSv1.1,TLSv1.2”);
Я имел дело с той же проблемой (Paypal TLS 1.2 против 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;
}
}
}
Моя проблема заключалась в том, что у меня была установлена Java 7, поэтому я обновился до Java 8 и вуаля, исключение больше не было:)