http2.0实战

生成证书

  • keystore以及服务器密钥对儿的生成
bogon:cert liukai$ keytool -genkeypair -alias rabbitsslkey -keyalg RSA -validity 3650 -keystore rabbitkeystore.jks
输入密钥库口令:
再次输入新口令:
您的名字与姓氏是什么?
  [Unknown]:  localhost
您的组织单位名称是什么?
  [Unknown]:  localhost
您的组织名称是什么?
  [Unknown]:  localhost
您所在的城市或区域名称是什么?
  [Unknown]:  localhost
您所在的省/市/自治区名称是什么?
  [Unknown]:  localhost
该单位的双字母国家/地区代码是什么?
  [Unknown]:  localhost
CN=localhost, OU=localhost, O=localhost, L=localhost, ST=localhost, C=localhost是否正确?
  [否]:  y

输入 <rabbitsslkey> 的密钥口令
	(如果和密钥库口令相同, 按回车):
  • 验证新生成的keystore文件以及证书信息
keytool -list -v -keystore rabbitkeystore.jks
  • 导出公钥证书
keytool -export -alias rabbitsslkey -keystore rabbitkeystore.jks -rfc -file rabbitcert.cer
  • Truststore的生成以及公钥证书的导入
keytool -import -alias rabbitsslkey -file rabbitcert.cer -keystore rabbittruststore.jks
  • 验证生成的truststore文件
keytool -list -v -keystore rabbittruststore.jks

新建spring boot项目

pom


  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.1.6.RELEASE</version>
    <relativePath/>
  </parent>

  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
      <exclusions>
        <exclusion>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-tomcat</artifactId>
        </exclusion>
      </exclusions>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-undertow</artifactId>
    </dependency>
  </dependencies>

application.yam

server:
  http2:
    enabled: true
  ssl:
    key-store: classpath:rabbitkeystore.jks
    key-store-password: rabbit
    key-password: rabbit
  port: 8443

api

package com.kevin.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.PushBuilder;

/**
 * Created by kevin on 2021/1/14.
 */
@RestController
@RequestMapping
public class IndexController {

    @GetMapping("/index")
    public String index(PushBuilder pushBuilder) {
        pushBuilder.path("/style.css").push();
        return "<html><head><link rel=\"stylesheet\" href=\"style.css\"></head></html>";
    }
    @GetMapping("/style.css")
    public String css() {
        return "css";
    }
    
    @GetMapping("/index/withoutpush")
    public String indexWithoutPush() {
        return "<html><head><link rel=\"stylesheet\" href=\"style.css\"></head></html>";
    }
}

访问

通过浏览器访问https://localhost:8443/index,打开wireshark抓取报文,报文如下

发现已正常解密https报文,并且标识为http2类型报文

client hello


浏览器在进行SSL连接,第一次发送ClientHello包时,用过SSL的扩展字段,携带浏览器支持的版本,其中 h2 代表浏览器支持http2协议。详见附1

server hello

服务器在返回Server Hello包时,如果服务器支持H2协议,则会返回H2,如果不支持,那么客户端的协议列表中选取一个它支持的协议。

接下来就可以以http2的方式发包了,这也是为什么Nginx配置了http2以后,还需要升级openssl到1.0.2,因为NGINX的SSL连接用的是OPENSSL这个库,这个库在Centos6.8上用的是1.0.1这个版本,这个版本不支持H2,所以在SSL秘钥协商时就不会返回支持H2的标识,所以还是用http1.1

http2 header

访问https://localhost:8443/index/withoutpush

  • 01000001:表示该header为不包含value的静态表头部,索引为1的为:authority,并且将该header保存为动态header
  • 10001010:1表示值是经过Huffman编码的,1010表示值的长度为10byte
  • 后10位表示字符串localhost:8443,不足的补1
    • 101000: l
    • 00111: o
    • 00100: c
    • 00011: a
    • 101000: l
    • 100111: h
    • 00111: o
    • 01000: s
    • 01001: t
    • 1011100: :
    • 011110: 8
    • 011010: 4
    • 011010: 4
    • 011001: 3
    • 1: 补位1

huffman编码采用的是树结构

当请求css时,发现之前header的值为 11001000

  • 首bit 1表示使用动态索引表
  • 1001000表示索引值为72
  • 发现比上一请求发送的header体大大减小

http2 push

通过上述图片发现

  • 服务器端直接向客户端push了css
  • 浏览器加载时,直接获取push的信息

http2的弊端

  • 有序字节流引出的队头阻塞(Head-of-line blocking),使得 HTTP/2 的多路复用能力大打折扣;
  • TCP 与 TLS 叠加了握手时延,建链时长还有 1 倍的下降空间;
  • 基于 TCP 四元组确定一个连接,这种诞生于有线网络的设计,并不适合移动状态下的无线网络,这意味着 IP 地址的频繁变动会导致 TCP 连接、TLS 会话反复握手,成本高昂。

而 HTTP/3 协议恰恰是解决了这些问题:

  • HTTP/3 基于 UDP 协议重新定义了连接,在 QUIC 层实现了无序、并发字节流的传输,解决了队头阻塞问题(包括基于 QPACK 解决了动态表的队头阻塞);
  • HTTP/3 重新定义了 TLS 协议加密 QUIC 头部的方式,既提高了网络攻击成本,又降低了建立连接的速度(仅需 1 个 RTT 就可以同时完成建链与密钥协商);
  • HTTP/3 将 Packet、QUIC Frame、HTTP/3 Frame 分离,实现了连接迁移功能,降低了 5G 环境下高速移动设备的连接维护成本。

问题

如果无法抓取http2报文,确认证书已导致系统