生成证书
- 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 环境下高速移动设备的连接维护成本。