如何强制Java SSLContext及Jersey客户端使用TLSv1.1协议
问题根因
- 高版本JDK(JDK 8u292、JDK 11及以上)默认在
java.security配置的jdk.tls.disabledAlgorithms中加入了TLSv1、TLSv1.1,即使你调用SSLContext.getInstance("TLSv1.1")创建上下文,JDK也会默认禁用该版本协议的握手。 - Jersey 1.x的
HTTPSProperties仅负责给HttpsURLConnection设置SSLSocketFactory和HostnameVerifier,不会对SSLSocket的启用协议做限制,你指定的TLSv1.1上下文不会限制客户端仅用该版本发起握手,默认仍会尝试使用最高可用的协议版本,或者被JDK全局配置禁用导致握手失败。 - 你配置的
https.protocols系统属性仅对JDK默认的SSLSocketFactory生效,自定义SSLContext后该配置会被忽略。
你使用SSLContext.getInstance("TLS")时可以正常运行,是因为此时客户端会使用JDK默认启用的高版本协议(TLSv1.2/TLSv1.3),和服务端支持的协议匹配,所以握手成功。
解决方案
步骤1:解除客户端JDK的TLSv1.1禁用限制
打开客户端JDK的安全配置文件:
- JDK 8路径:
jre/lib/security/java.security - JDK 11及以上路径:
conf/security/java.security
找到jdk.tls.disabledAlgorithms配置项,删除其中的TLSv1、TLSv1.1条目并保存。
步骤2:自定义SSLSocketFactory强制指定协议版本
因为Jersey 1.x没有提供直接配置启用协议的入口,你需要自定义SSLSocketFactory包装类,强制所有创建的SSLSocket仅使用TLSv1.1协议:
import javax.net.ssl.SSLSocket; import javax.net.ssl.SSLSocketFactory; import java.io.IOException; import java.net.InetAddress; import java.net.Socket; public class FixedTLSVersionSocketFactory extends SSLSocketFactory { private final SSLSocketFactory delegateFactory; private static final String[] ALLOWED_PROTOCOLS = {"TLSv1.1"}; public FixedTLSVersionSocketFactory(SSLSocketFactory delegate) { this.delegateFactory = delegate; } private Socket configureTLSVersion(Socket socket) { if (socket instanceof SSLSocket) { ((SSLSocket) socket).setEnabledProtocols(ALLOWED_PROTOCOLS); } return socket; } @Override public Socket createSocket() throws IOException { return configureTLSVersion(delegateFactory.createSocket()); } @Override public Socket createSocket(String host, int port) throws IOException { return configureTLSVersion(delegateFactory.createSocket(host, port)); } @Override public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException { return configureTLSVersion(delegateFactory.createSocket(host, port, localHost, localPort)); } @Override public Socket createSocket(InetAddress host, int port) throws IOException { return configureTLSVersion(delegateFactory.createSocket(host, port)); } @Override public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException { return configureTLSVersion(delegateFactory.createSocket(address, port, localAddress, localPort)); } @Override public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException { return configureTLSVersion(delegateFactory.createSocket(s, host, port, autoClose)); } @Override public String[] getDefaultCipherSuites() { return delegateFactory.getDefaultCipherSuites(); } @Override public String[] getSupportedCipherSuites() { return delegateFactory.getSupportedCipherSuites(); } }
步骤3:修改Jersey客户端配置
由于HTTPSProperties仅接受SSLContext作为参数,选择自定义URLConnectionClientHandler来设置自定义的SocketFactory的方案更稳定:
import com.sun.jersey.client.urlconnection.URLConnectionClientHandler; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLContext; import java.net.HttpURLConnection; public class TLSConfiguredClientHandler extends URLConnectionClientHandler { private final HostnameVerifier hostnameVerifier; private final FixedTLSVersionSocketFactory socketFactory; public TLSConfiguredClientHandler(HostnameVerifier hv, SSLContext sslContext) { this.hostnameVerifier = hv; this.socketFactory = new FixedTLSVersionSocketFactory(sslContext.getSocketFactory()); } @Override protected void modifyConnection(HttpURLConnection connection) { if (connection instanceof HttpsURLConnection) { HttpsURLConnection httpsConn = (HttpsURLConnection) connection; httpsConn.setHostnameVerifier(hostnameVerifier); httpsConn.setSSLSocketFactory(socketFactory); } super.modifyConnection(connection); } }
最后创建Jersey客户端时使用自定义的Handler即可:
SSLContext context = SSLContext.getInstance("TLSv1.1"); context.init(null, trustAll, new SecureRandom()); Client client = new Client(new TLSConfiguredClientHandler(hv, context));
内容的提问来源于stack exchange,提问作者Philippe Bertrand
相关产品推荐
相关产品推荐

