简介

咱们晓得尽管HTTP2协定并不强制应用HTTPS,然而对大多数浏览器来说,如果要应用HTTP2的话,则必须应用HTTPS,所以咱们须要理解如何在netty的TLS中反对http2。

TLS的扩大协定NPN和ALPN

HTTP2协定是从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>

通过测试,完满执行。

ApplicationProtocolConfig

ApplicationProtocolConfig是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_ADVERTISE                    SelectorFailureBehavior.NO_ADVERTISE,                    // 目前 OpenSsl 和 JDK providers只反对ACCEPT                    SelectedListenerFailureBehavior.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/

最艰深的解读,最粗浅的干货,最简洁的教程,泛滥你不晓得的小技巧等你来发现!

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