提供给正在弄微信支付踩坑的你,赶快弄完去干其他重要的事情!!!
测微信支付然后退款,出现这种问题是不是觉着很神奇,代码也没动,啥也没改为啥就出现问题了。线上也好好的,本地一测试就报出了下面的错误:
[ERROR][11:42:56] WechatRefundHelper:161 No appropriate protocol (protocol is disabled or cipher suites are inappropriate)
javax.net.ssl.SSLHandshakeException: No appropriate protocol (protocol is disabled or cipher suites are inappropriate)
at sun.security.ssl.HandshakeContext.<init>(HandshakeContext.java:171)
at sun.security.ssl.ClientHandshakeContext.<init>(ClientHandshakeContext.java:98)
at sun.security.ssl.TransportContext.kickstart(TransportContext.java:220)
at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:428)
at org.apache.http.conn.ssl.SSLConnectionSocketFactory.createLayeredSocket(SSLConnectionSocketFactory.java:395)
at org.apache.http.conn.ssl.SSLConnectionSocketFactory.connectSocket(SSLConnectionSocketFactory.java:354)
at org.apache.http.impl.conn.DefaultHttpClientConnectionOperator.connect(DefaultHttpClientConnectionOperator.java:134)
at org.apache.http.impl.conn.PoolingHttpClientConnectionManager.connect(PoolingHttpClientConnectionManager.java:353)
at org.apache.http.impl.execchain.MainClientExec.establishRoute(MainClientExec.java:380)
at org.apache.http.impl.execchain.MainClientExec.execute(MainClientExec.java:236)
at org.apache.http.impl.execchain.ProtocolExec.execute(ProtocolExec.java:184)
at org.apache.http.impl.execchain.RetryExec.execute(RetryExec.java:88)
at org.apache.http.impl.execchain.RedirectExec.execute(RedirectExec.java:110)
at org.apache.http.impl.client.InternalHttpClient.doExecute(InternalHttpClient.java:184)
at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:82)
at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:107)
出现这个问题的原因其实是 微信支付系统
和 JDK
或 jre
的不兼容,解决方式也主要针对于这两个方面,遇到的时候可以都尝试一下。建议先尝试修改代码,不行的话去查找对应的环境,真不行的话最后去修改 JDK 目录
。
1. 直接更改微信支付提供的代码(推荐)
这里就还是需要吐槽一下,这代码是不是好久没有更新了,哈哈哈!!!
官方 SDK 地址: https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=11_1
// 官方提供的坑代码
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(kmf.getKeyManagers(), null, new SecureRandom());
SSLConnectionSocketFactory sslConnectionSocketFactory =
new SSLConnectionSocketFactory(
sslContext,
// 问题就出在下面这句话,直接改为 null
new String[]{"TLSv1"},
null,
new DefaultHostnameVerifier());
connManager = new BasicHttpClientConnectionManager(
RegistryBuilder.<ConnectionSocketFactory>create()
.register("http",
PlainConnectionSocketFactory.getSocketFactory())
.register("https", sslConnectionSocketFactory)
.build(), null, null, null
);
修改后如下:
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(kmf.getKeyManagers(), null, new SecureRandom());
SSLConnectionSocketFactory sslConnectionSocketFactory =
new SSLConnectionSocketFactory(
sslContext,
null,
null,
new DefaultHostnameVerifier());
connManager = new BasicHttpClientConnectionManager(
RegistryBuilder.<ConnectionSocketFactory>create()
.register("http",
PlainConnectionSocketFactory.getSocketFactory())
.register("https", sslConnectionSocketFactory)
.build(), null, null, null
);
【注】如果这种方式可以的话就强烈建议这种方式,不行的话就继续往下看。。。
2. 查找匹配的 JDK/jre 环境
如果是一上来开发就遇到还真的不好排查,但是我遇到的是线上没有问题,本地出现了这种问题,刚开始以为还是代码出现了 bug
后来发现,其实就是 JDK/jre
的问题。线上使用的是 OpenJDK
,版本如下:
openjdk version "1.8.0_282"
OpenJDK Runtime Environment (build 1.8.0_282-b08)
OpenJDK 64-Bit Server VM (build 25.282-b08, mixed mode)
然而我本地使用的是:
openjdk version "1.8.0_292"
OpenJDK Runtime Environment (build 1.8.0_292-b10)
OpenJDK 64-Bit Server VM (build 25.292-b10, mixed mode)
但是版本差的那么小,应该是没啥问题,但是实际就是有这样的问题,最终也是换到与线上环境版本一致的 JDK 版本然后就没有问题。
这让我想起来之前遇到的也是 JDK 的版本问题: Error:(16, 42) java: package com.sun.org.apache.regexp.internal does not exist
都是泪 !!!
【注】如果是高于 JDK/jre 8
的还是使用先尝试第一种方式。不行的话就往下降低版本,然后结合方法三查找合适的 JDK/jre
版本。
3. 直接修改 jre 中 java.security 的默认限制
其实是不推荐这样改的,如果真到了这一步(第一种和第二种方式都阵亡了),那么就使用这种方式吧。
其实第二种换版本的方式也是为了解决 java.security
的默认限制,方法二示例中这两个 JDK/jre
的 java.security
配置。
- 线上
OpenJDK 1.8.0_282
的java.security
:
// ....
jdk.tls.disabledAlgorithms=SSLv3, RC4, DES, MD5withRSA, DH keySize < 1024, \
EC keySize < 224, 3DES_EDE_CBC, anon, NULL
// ....
- 本地
OpenJDK 1.8.0_292
的java.security
:
// ....
jdk.tls.disabledAlgorithms=SSLv3, TLSv1, TLSv1.1, RC4, DES, MD5withRSA, \
DH keySize < 1024, EC keySize < 224, 3DES_EDE_CBC, anon, NULL, \
include jdk.disabled.namedCurves
// ....
【注】jdk.tls.disabledAlgorithms
:tls
的禁用策略。注释中也明确说明了:Disabled algorithms will not be negotiated for SSL/TLS connections, even if they are enabled explicitly in an application.
(禁用的算法将不会为SSL/TLS连接进行协商,即使它们在应用程序中显式地启用)。
解决方法:
方法也不麻烦,先找到 JDK/jre
的位置(因为 Java
项目运行的时候使用的是 jre
,只有在开发和编译的时候才会使用 JDK
。这里要注意下,因为 JDK
是包含 jre
的,线上改的时候可以只改 jre
目录即可;如果是本地开发,记得也要修改 JDK
中的配置):
# 以 jre 为例:
# 查找 java 命令的位置,一般 java 应该在 jre/bin 目录下;有的环境也有可能被设置为一个链接
which java
/usr/local/openjdk-8/bin/java
# 根据实际路径处理, jre 的位置一般在 JAVA_HOME 目录下(我这里的是 /usr/local/openjdk-8 )
# 进入 java.security 所在的目录
cd /usr/local/openjdk-8/jre/lib/security
# 接下来修改 java.security 即可,主要是修改 jdk.tls.disabledAlgorithms,对于微信的退款来说
# 将 TLSv1 从里面删掉即可,这样的话就可以使用了。
vim java.security
【注】其实这个方式还是有点坑的,就是使用的自定义修改后的 JDK/jre
环境,对于协同开发来说做不到很好的统一,所以不是很推荐,建议还是查找合适的 JDK/jre
处理。