写点什么

netty 系列之: 让 TLS 支持 http2

发布于: 刚刚

简介我们知道虽然 HTTP2 协议并不强制使用 HTTPS,但是对大多数浏览器来说,如果要使用 HTTP2 的话,则必须使用 HTTPS,所以我们需要了解如何在 netty 的 TLS 中支持 http2。


TLS 的扩展协议 NPN 和 ALPNHTTP2 协议是从 spdy 协议发展而来的,无论是 spdy 还是 http2 都为了能在 HTTPS 的环境下工作,发展出来了 TLS 协议的扩展。


他们分别叫做 NPN(Next Protocol Negotiation) 和 ALPN (Application Layer Protocol Negotiation) 。


他们规定了在 TLS 协议握手之后,客户端和服务器端进行应用数据通信的协议。其中 ALPN 可以在客户端首次和服务器端进行握手的时候,就列出客户端支持的应用层数据协议,服务器端直接选择即可,因此可以比 NPN 少一个交互流程,更加优秀。


那么 spdy 和 http2 分别支持的协议都有哪些呢?


netty 提供了一个 ApplicationProtocolNames 类,在其中定义了各自对应的协议,其中 ALPN 对应了 http2 和 http1.1,而 sydy 对应了 spdy/1,spdy/2,spdy/3:


/** * HTTP version 2 */public static final String HTTP_2 = "h2";
/** * {@code "http/1.1"}: HTTP version 1.1 */public static final String HTTP_1_1 = "http/1.1";
/** * {@code "spdy/3.1"}: SPDY version 3.1 */public static final String SPDY_3_1 = "spdy/3.1";
/** * {@code "spdy/3"}: SPDY version 3 */public static final String SPDY_3 = "spdy/3";
/** * {@code "spdy/2"}: SPDY version 2 */public static final String SPDY_2 = "spdy/2";
/** * {@code "spdy/1"}: SPDY version 1 */public static final String SPDY_1 = "spdy/1";
复制代码


SslProvider 目前来说,netty 中有两种 SSL 的实现方式,一种是 JDK,一种是 OPENSSL,不同的实现方式对 TLS 协议扩展的支持也不一样。它提供了一个 isAlpnSupported 方法,根据传入 provider 的不同来判断,是否支持 ALPN。


public static boolean isAlpnSupported(final SslProvider provider) {    switch (provider) {        case JDK:            return JdkAlpnApplicationProtocolNegotiator.isAlpnSupported();        case OPENSSL:        case OPENSSL_REFCNT:            return OpenSsl.isAlpnSupported();        default:            throw new Error("Unknown SslProvider: " + provider);    }}
复制代码


如果你使用的是 JDK8,那么运行之后,可能会得到下面的错误提示:


ALPN is only supported in Java9 or if you use conscrypt as your provider or have the jetty alpn stuff on the class path.也就是说如果是用 JDK 作为默认的 SSL provider 的话,它是不支持 ALPN 的。必须升级到 java9.


根据提示如果添加 conscrypt 到 classpath 中:


    <dependency>        <groupId>org.conscrypt</groupId>        <artifactId>conscrypt-openjdk-uber</artifactId>        <version>2.5.2</version>    </dependency>
复制代码


运行之后会得到下面的错误:


Unable to wrap SSLEngine of type 'sun.security.ssl.SSLEngineImpl'怎么办呢?答案就是使用 Open SSL,还需要添加:


    <dependency>        <groupId>io.netty</groupId>        <artifactId>netty-tcnative-boringssl-static</artifactId>        <version>2.0.40.Final</version>    </dependency>
复制代码


经过测试,完美执行。


ApplicationProtocolConfigApplicationProtocolConfig 是 netty 提供了传递给 SSLEngine 的协议配置类,它主要有四个属性:


private final List<String> supportedProtocols;private final Protocol protocol;private final SelectorFailureBehavior selectorBehavior;private final SelectedListenerFailureBehavior selectedBehavior;
复制代码


supportedProtocols 是支持的数据传输协议,像上面的 HTTP2,HTTP1.1 或者 spdy/1,spdy/2,spdy/3 等。


protocol 是 TLS 的扩展协议,像 ALPN 或者 NPN 等。


selectorBehavior 是在选择协议的时候的表现方式,有 3 种方式:


FATAL_ALERT: 如果选择应用程序协议的节点没有找到匹配项,那么握手将会失败。NO_ADVERTISE: 如果选择应用程序协议的节点没有找到匹配项,它将通过在握手中假装不支持 TLS 扩展。CHOOSE_MY_LAST_PROTOCOL: 如果选择应用程序协议的节点没有找到匹配项,将会使用上一次建议使用的协议。


selectedBehavior 是通知被选择的协议之后的表现方式,也有 3 种方式:


ACCEPT: 如果节点不支持对方节点选择的应用程序协议,则该节点默认不支持该 TLS 扩展,然后继续握手。FATAL_ALERT: 如果节点不支持对方节点选择的应用程序协议,则握手失败。CHOOSE_MY_LAST_PROTOCOL: 如果节点不支持对方节点选择的应用程序协议,将会使用上一次建议使用的协议。


构建 SslContext 有了 provider,ApplicationProtocolConfig 之后,就可以构建 SslContext 了。首先创建 SSL provider:


SslProvider provider = SslProvider.isAlpnSupported(SslProvider.OPENSSL) ? SslProvider.OPENSSL : SslProvider.JDK;


默认情况下使用 JDK 作为 ssl provider,如果你使用的是 OpenSSL 的话,就使用 OpenSSL。


我们使用 SslContextBuilder.forServer 来创建 SslContext,这个方法需要传入 certificate 和 privateKey,为了简单起见,我们使用自签名的 SelfSignedCertificate:


SelfSignedCertificate ssc = new SelfSignedCertificate();sslCtx = SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey()).build();还可以为其设置 sslProvider,ciphers 和 applicationProtocolConfig 等信息:


sslCtx = SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey()).sslProvider(provider)//支持的 cipher.ciphers(Http2SecurityUtil.CIPHERS, SupportedCipherSuiteFilter.INSTANCE).applicationProtocolConfig(new ApplicationProtocolConfig(Protocol.ALPN,// 目前 OpenSsl 和 JDK providers 只支持 NO_ADVERTISESelectorFailureBehavior.NO_ADVERTISE,// 目前 OpenSsl 和 JDK providers 只支持 ACCEPTSelectedListenerFailureBehavior.ACCEPT,ApplicationProtocolNames.HTTP_2,ApplicationProtocolNames.HTTP_1_1)).build();ProtocolNegotiationHandler 最后,我们需要根据协商使用的不同协议,进行不同的处理。netty 提供了一个 ApplicationProtocolNegotiationHandler,自定义的话,只需要继承该类即可,比如,我们根据 protocol 的名称不同,来分别处理 HTTP1 和 HTTP2 请求:


public class MyNegotiationHandler extends ApplicationProtocolNegotiationHandler {public MyNegotiationHandler() {super(ApplicationProtocolNames.HTTP_1_1);}


   protected void configurePipeline(ChannelHandlerContext ctx, String protocol) {       if (ApplicationProtocolNames.HTTP_2.equals(protocol) {           configureHttp2(ctx);       } else if (ApplicationProtocolNames.HTTP_1_1.equals(protocol)) {           configureHttp1(ctx);       } else {           throw new IllegalStateException("unknown protocol: " + protocol);       }   }
复制代码


}然后将其加入到 ChannelPipeline 中即可:


public class MyInitializer extends ChannelInitializer<Channel> {private final SslContext sslCtx;


   public MyInitializer(SslContext sslCtx) {       this.sslCtx = sslCtx;   }
protected void initChannel(Channel ch) { ChannelPipeline p = ch.pipeline(); p.addLast(sslCtx.newHandler(...)); // Adds SslHandler p.addLast(new MyNegotiationHandler()); }
复制代码


}总结以上就是在 netty 中配置 TLS 支持 HTTP2 的完整流程了。


本文的例子可以参考:learn-netty4


本文已收录于 http://www.flydean.com/26-netty-secure-http2/


最通俗的解读,最深刻的干货,最简洁的教程,众多你不知道的小技巧等你来发现!


欢迎关注我的公众号:「程序那些事」,懂技术,更懂你!

发布于: 刚刚阅读数: 2
用户头像

关注公众号:程序那些事,更多精彩等着你! 2020.06.07 加入

最通俗的解读,最深刻的干货,最简洁的教程,众多你不知道的小技巧,尽在公众号:程序那些事!

评论

发布
暂无评论
netty系列之:让TLS支持http2