使用 jdk8 加密以启用 http2 的 ALPN
Posted
技术标签:
【中文标题】使用 jdk8 加密以启用 http2 的 ALPN【英文标题】:Conscrypt with jdk8 to enable ALPN for http2 【发布时间】:2019-04-22 13:10:54 【问题描述】:我一直在寻找如何使用 conscrypt-openjdk-uber-1.4.1.jar 为 jdk8 实现 Conscrypt SSL 提供程序以支持 ALPN 用于与服务器建立 http2(使用 apache httpclient 5) 连接,因为 jdk8 默认不支持 ALPN 或者其他解决方案是迁移到 jdk9(或更高版本)目前这是不可行的,因为我们的产品严重依赖 jdk8
我一直在广泛搜索一些要实施的文档或示例,但找不到。
我尝试将 conscrypt 提供程序作为默认插入,我的程序将其作为默认提供程序,但仍然无法连接 http2 服务器,我的示例如下,
public static void main(final String[] args) throws Exception
Security.insertProviderAt(new OpenSSLProvider(), 1);
final SSLContext sslContext = SSLContexts.custom().loadTrustMaterial(new TrustAllStrategy()).build();
final PoolingAsyncClientConnectionManager connectionManager = PoolingAsyncClientConnectionManagerBuilder.create().setTlsStrategy(new H2TlsStrategy(sslContext, NoopHostnameVerifier.INSTANCE)).build();
final IOReactorConfig ioReactorConfig = IOReactorConfig.custom().setSoTimeout(Timeout.ofSeconds(5)).build();
final MinimalHttpAsyncClient client = HttpAsyncClients.createMinimal(HttpVersionPolicy.FORCE_HTTP_2, H2Config.DEFAULT, null, ioReactorConfig, connectionManager);
client.start();
final HttpHost target = new HttpHost("localhost", 8082, "https");
final Future<AsyncClientEndpoint> leaseFuture = client.lease(target, null);
final AsyncClientEndpoint endpoint = leaseFuture.get(10, TimeUnit.SECONDS);
try
String[] requestUris = new String[] "/";
CountDownLatch latch = new CountDownLatch(requestUris.length);
for (final String requestUri: requestUris)
SimpleHttpRequest request = SimpleHttpRequest.get(target, requestUri);
endpoint.execute(SimpleRequestProducer.create(request), SimpleResponseConsumer.create(), new FutureCallback<SimpleHttpResponse>()
@Override
public void completed(final SimpleHttpResponse response)
latch.countDown();
System.out.println(requestUri + "->" + response.getCode());
System.out.println(response.getBody());
@Override
public void failed(final Exception ex)
latch.countDown();
System.out.println(requestUri + "->" + ex);
ex.printStackTrace();
@Override
public void cancelled()
latch.countDown();
System.out.println(requestUri + " cancelled");
);
latch.await();
catch (Exception e)
e.printStackTrace();
finally
endpoint.releaseAndReuse();
client.shutdown(ShutdownType.GRACEFUL);
这个程序给出的输出为
org.apache.hc.core5.http.ConnectionClosedException:连接关闭 org.apache.hc.core5.http.ConnectionClosedException:连接关闭 在 org.apache.hc.core5.http2.impl.nio.FrameInputBuffer.read(FrameInputBuffer.java:146) 在 org.apache.hc.core5.http2.impl.nio.AbstractHttp2StreamMultiplexer.onInput(AbstractHttp2StreamMultiplexer.java:415) 在 org.apache.hc.core5.http2.impl.nio.AbstractHttp2IOEventHandler.inputReady(AbstractHttp2IOEventHandler.java:63) 在 org.apache.hc.core5.http2.impl.nio.ClientHttp2IOEventHandler.inputReady(ClientHttp2IOEventHandler.java:38) 在 org.apache.hc.core5.reactor.InternalDataChannel.onIOEvent(InternalDataChannel.java:117) 在 org.apache.hc.core5.reactor.InternalChannel.handleIOEvent(InternalChannel.java:50) 在 org.apache.hc.core5.reactor.SingleCoreIOReactor.processEvents(SingleCoreIOReactor.java:173) 在 org.apache.hc.core5.reactor.SingleCoreIOReactor.doExecute(SingleCoreIOReactor.java:123) 在 org.apache.hc.core5.reactor.AbstractSingleCoreIOReactor.execute(AbstractSingleCoreIOReactor.java:80) 在 org.apache.hc.core5.reactor.IOReactorWorker.run(IOReactorWorker.java:44) 在 java.lang.Thread.run(Thread.java:748)
如果我打印提供程序和版本,它会打印为 Conscrypt 版本 1.0 和 JDK 1.8.0_162,但仍然无法连接到 http2 端点
如果我使用 jdk9 与默认提供程序连接,相同的代码块可以完美运行,我在 conscrypt 配置中缺少什么?
感谢任何帮助
提前致谢
【问题讨论】:
我知道 Jetty 使用 Java 8 和 Conscrypt。码头包括client side library。这是通过 HTTP/2 连接到服务器的example。也许这可能是您的替代方案。 感谢您的建议,但我的要求是 apache httpclient 【参考方案1】:仅仅用 Conscrypt 替换默认的 JSSE 提供者是不够的。还需要一个可以利用 Conscrypt API 的自定义 TlsStrategy
。
这对我来说适用于 Java 1.8 和 Conscrypt 1.4.1
static class ConscriptClientTlsStrategy implements TlsStrategy
private final SSLContext sslContext;
public ConscriptClientTlsStrategy(final SSLContext sslContext)
this.sslContext = Args.notNull(sslContext, "SSL context");
@Override
public boolean upgrade(
final TransportSecurityLayer tlsSession,
final HttpHost host,
final SocketAddress localAddress,
final SocketAddress remoteAddress,
final Object attachment)
final String scheme = host != null ? host.getSchemeName() : null;
if (URIScheme.HTTPS.same(scheme))
tlsSession.startTls(
sslContext,
host,
SSLBufferMode.STATIC,
(endpoint, sslEngine) ->
final SSLParameters sslParameters = sslEngine.getSSLParameters();
sslParameters.setProtocols(H2TlsSupport.excludeBlacklistedProtocols(sslParameters.getProtocols()));
sslParameters.setCipherSuites(H2TlsSupport.excludeBlacklistedCiphers(sslParameters.getCipherSuites()));
H2TlsSupport.setEnableRetransmissions(sslParameters, false);
final HttpVersionPolicy versionPolicy = attachment instanceof HttpVersionPolicy ?
(HttpVersionPolicy) attachment : HttpVersionPolicy.NEGOTIATE;
final String[] appProtocols;
switch (versionPolicy)
case FORCE_HTTP_1:
appProtocols = new String[] ApplicationProtocols.HTTP_1_1.id ;
break;
case FORCE_HTTP_2:
appProtocols = new String[] ApplicationProtocols.HTTP_2.id ;
break;
default:
appProtocols = new String[] ApplicationProtocols.HTTP_2.id, ApplicationProtocols.HTTP_1_1.id ;
if (Conscrypt.isConscrypt(sslEngine))
sslEngine.setSSLParameters(sslParameters);
Conscrypt.setApplicationProtocols(sslEngine, appProtocols);
else
H2TlsSupport.setApplicationProtocols(sslParameters, appProtocols);
sslEngine.setSSLParameters(sslParameters);
,
(endpoint, sslEngine) ->
if (Conscrypt.isConscrypt(sslEngine))
return new TlsDetails(sslEngine.getSession(), Conscrypt.getApplicationProtocol(sslEngine));
return null;
);
return true;
return false;
public static void main(String[] args) throws Exception
final SSLContext sslContext = SSLContexts.custom()
.setProvider(Conscrypt.newProvider())
.build();
final PoolingAsyncClientConnectionManager cm = PoolingAsyncClientConnectionManagerBuilder.create()
.setTlsStrategy(new ConscriptClientTlsStrategy(sslContext))
.build();
try (CloseableHttpAsyncClient client = HttpAsyncClients.custom()
.setVersionPolicy(HttpVersionPolicy.NEGOTIATE)
.setConnectionManager(cm)
.build())
client.start();
final HttpHost target = new HttpHost("nghttp2.org", 443, "https");
final String requestUri = "/httpbin";
final HttpClientContext clientContext = HttpClientContext.create();
final SimpleHttpRequest request = SimpleHttpRequests.GET.create(target, requestUri);
final Future<SimpleHttpResponse> future = client.execute(
SimpleRequestProducer.create(request),
SimpleResponseConsumer.create(),
clientContext,
new FutureCallback<SimpleHttpResponse>()
@Override
public void completed(final SimpleHttpResponse response)
System.out.println(requestUri + "->" + response.getCode() + " " +
clientContext.getProtocolVersion());
System.out.println(response.getBody());
final SSLSession sslSession = clientContext.getSSLSession();
if (sslSession != null)
System.out.println("SSL protocol " + sslSession.getProtocol());
System.out.println("SSL cipher suite " + sslSession.getCipherSuite());
@Override
public void failed(final Exception ex)
System.out.println(requestUri + "->" + ex);
@Override
public void cancelled()
System.out.println(requestUri + " cancelled");
);
future.get();
System.out.println("Shutting down");
client.shutdown(CloseMode.GRACEFUL);
【讨论】:
这只需少量修改即可工作,感谢您的努力@oleg @Zyber 您可能希望在此处或与 Apache HttpComponents 分享您的修改 这些变化大多像拼写错误,所以我没有分享,无论如何我会分享,以便它可以帮助像我这样的人1。 tlsSession.startTls 只接受 4 个参数(sslcontext、SSLBufferManagement、SSLSessionInitializer、SSLSessionVerifier)它不接受 host 作为参数 2。上述方法中的第二个参数只接受 SSLBufferManagement 不正如代码中的 SSLBufferMode 一样 sn -p 3.SimpleHttpRequests.GET.create(target, requestUri) 可以替换为 SimpleHttpRequest.get(target, requestUri) 4.CloseMode .GRACEFUL 可以替换为 ShutDownType.GRACEFUL 而你的sn-p使用了CloseableHttpClient,我已将其改为MinimalHttpAsyncClient用于在http2中复用 您仍在使用 5.0 BETA1。我建议升级到 5.0 BETA2以上是关于使用 jdk8 加密以启用 http2 的 ALPN的主要内容,如果未能解决你的问题,请参考以下文章
nginx如何启用对HTTP2的支持 | nginx如何验证HTTP2是否已启用