微信退款 No appropriate protocol (protocol is disabled or cipher suites are inappropriate)

提供给正在弄微信支付踩坑的你,赶快弄完去干其他重要的事情!!!

测微信支付然后退款,出现这种问题是不是觉着很神奇,代码也没动,啥也没改为啥就出现问题了。线上也好好的,本地一测试就报出了下面的错误:

[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)

出现这个问题的原因其实是 微信支付系统JDKjre 的不兼容,解决方式也主要针对于这两个方面,遇到的时候可以都尝试一下。建议先尝试修改代码,不行的话去查找对应的环境,真不行的话最后去修改 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/jrejava.security 配置。

  • 线上 OpenJDK 1.8.0_282java.security
// ....

jdk.tls.disabledAlgorithms=SSLv3, RC4, DES, MD5withRSA, DH keySize < 1024, \
    EC keySize < 224, 3DES_EDE_CBC, anon, NULL

// ....
  • 本地 OpenJDK 1.8.0_292java.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.disabledAlgorithmstls 的禁用策略。注释中也明确说明了: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 处理。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注