TLS 握手是 HTTPS 连接建立的开销大头。每次完整握手都需要额外的往返时延和计算资源。Session Resumption(会话恢复) 技术允许客户端和服务器复用之前的会话密钥,跳过昂贵的公钥加密步骤,大幅降低连接延迟。

为什么要会话恢复?

指标 完整握手 会话恢复
往返次数 2-RTT 或 1-RTT 0-RTT
RTT 时间 ~30-100ms ~5-10ms
CPU 消耗 高(RSA/ECDHE) 极低
适用场景 首次连接 短间隔重复访问

对于需要频繁建立连接的 HTTPS 场景(如 Web 攻击面扫描、API 调用),会话恢复可以将 TLS 握手耗时降低 80% 以上。

Session Resumption 的三种方式

1. Session ID(TLS 1.2 及更早)

服务端维护会话缓存,用 Session ID 作为 key。

1
2
3
4
5
# 客户端:保存会话
echo "" | openssl s_client -connect example.com:443 -sess_out session.pem

# 客户端:恢复会话
echo "" | openssl s_client -connect example.com:443 -sess_in session.pem

服务端配置(Nginx):

1
2
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;

服务端配置(Apache):

1
2
3
SSLStrictSNIVHostCheck off
SSLSessionCache shmcb:/tmp/ssl_scache(512000)
SSLSessionCacheTimeout 86400

2. Session Ticket(TLS 1.2 及更早)

服务端将会话密钥加密后发送给客户端,客户端存储并在下次请求时带回。

1
2
# 禁用 session ticket(测试用)
echo "" | openssl s_client -connect example.com:443 -no_ticket

问题: Session Ticket 需要服务端维护加密密钥,密钥轮换时可能导致旧 ticket 失效。

3. PSK(Pre-Shared Key,TLS 1.3)

TLS 1.3 将会话恢复统一为 PSK 机制,分为两种模式:

  • PSK+Handshake:结合完整握手,允许恢复会话
  • 0-RTT:仅使用 PSK,完全跳过握手
1
2
3
# 使用 PSK 连接
PSK_KEY=$(openssl rand -hex 32)
echo "" | openssl s_client -psk $PSK_KEY -connect example.com:443

服务端配置(OpenSSL s_server):

1
2
3
4
5
# 启用 0-RTT
openssl s_server -cert server.crt -key server.key -early_data

# 设置 PSK
openssl s_server -psk abcdef0123456789 -psk_hint myidentity

实战验证

使用 s_client 测试会话恢复

1
2
3
4
5
6
7
# 1. 首次连接,保存 session
echo "" | openssl s_client -connect example.com:443 \
    -servername example.com -sess_out session.pem

# 2. 使用保存的 session 重连
echo "" | openssl s_client -connect example.com:443 \
    -servername example.com -sess_in session.pem

观察输出:如果是恢复的会话,会显示 Reused 或相同的 Session ID。

测试 0-RTT(TLS 1.3)

1
2
3
# 使用 TLS 1.3 的 0-RTT 模式
openssl s_client -connect example.com:443 -tls1_3 \
    -early_data early_data.bin

调试 session 相关信息

1
2
3
4
5
# 查看连接后的 session 信息
echo "Q" | openssl s_client -connect example.com:443 -prexit

# 强制重新连接(使用相同 Session ID)
echo "" | openssl s_client -connect example.com:443 -reconnect

会话恢复的安全考量

1. 0-RTT 重放攻击

0-RTT 数据可能被攻击者截获并重放。TLS 1.3 通过以下方式缓解:

  • 只允许 idempotent 请求(GET、HEAD 等)
  • 服务器端限制 0-RTT 数据有效期
  • 应用层应实现唯一请求 ID

Nginx 配置(限制 0-RTT):

1
2
ssl_prefer_server_ciphers on;
ssl_session_tickets off;  # 生产环境建议关闭

2. 前向安全性

使用 Session Ticket 时,确保 Ticket 密钥定期轮换:

1
2
3
# OpenSSL s_server 配置
openssl s_server -cert server.crt -key server.key \
    -num_tickets 2  # 每次握手发行 2 个 ticket

3. Session 缓存大小

缓存过小会导致频繁失效,缓存过大浪费内存:

1
2
3
# Nginx 建议配置
ssl_session_cache shared:SSL:10m;  # 10MB 缓存
ssl_session_timeout 1d;             # 24小时过期

各版本对比

特性 TLS 1.2 TLS 1.3
Session ID
Session Ticket ✅ (简化)
PSK
0-RTT
恢复握手 1-RTT 0-RTT

性能测试

使用 curl 测试会话恢复效果:

1
2
3
4
5
6
# 首次连接(无恢复)
curl -w "Time: %{time_total}s\n" -o /dev/null -s https://example.com/

# 后续连接(使用 session)
# curl 会自动复用 TLS 会话
curl -w "Time: %{time_total}s\n" -o /dev/null -s https://example.com/

使用 Chrome DevTools 验证:

  1. 打开 DevTools → Network
  2. 刷新页面,点击任意 HTTPS 请求
  3. 查看 Connection ID,相同 ID 表示会话被复用

常见问题排查

会话未恢复

  1. 检查协议版本:两端都支持 TLS 1.2 或 1.3
  2. 检查缓存配置:服务端是否配置了 session cache
  3. 检查票据:客户端是否接受 session ticket
  4. 检查时间:session 是否在超时时间内
1
2
# 调试 TLS 连接
openssl s_client -connect example.com:443 -debug -state

性能提升不明显

  1. 检查是否使用了 TLS 1.3 + 0-RTT
  2. 确认客户端和服务器都启用了会话恢复
  3. 检查负载均衡器是否破坏了会话(需要 sticky session)

总结

场景 推荐方案
通用 HTTPS TLS 1.3 + PSK
HTTP/2 长连接 Session Ticket
API 高频调用 0-RTT
敏感数据 禁用 0-RTT

会话恢复是 HTTPS 性能优化的关键手段。根据实际场景选择合适的恢复模式,可以在保证安全性的前提下显著降低连接延迟。


参考来源