乐趣区

关于netty:netty系列之让TLS支持http2

简介

咱们晓得尽管 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/

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

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

退出移动版