bigywater.tech
Java · 251

ALPN:TLS 握手如何协商应用层协议

ALPN 让 TLS 握手顺便协商应用层协议(h2 还是 http/1.1)。谁的偏好优先?没有交集会怎样?一方没有设置会怎样?这篇用 JDK 的 SSLSocket、真实的 nginx、openssl s_client、curl 和 Java HttpClient 逐项验证。

服务器顺序
协商时谁的偏好优先(JDK SSLSocket 与 nginx 都是)
alert 120
没有交集时收到的 TLS 警告(no_application_protocol)
HTTP_1_1
服务器没有开 http2 时 Java HttpClient 请求 HTTP_2 实际得到的响应版本

协议列表与顺序

实测 · 本机执行输出
  [TLSv1.3]
    客户端 [h2, http/1.1]  服务器 [http/1.1]     → 客户端 getApplicationProtocol() = "http/1.1",协议 TLSv1.3;服务器 getApplicationProtocol() = "http/1.1"
    客户端 [h2, http/1.1]  服务器 [h2, http/1.1] → 客户端 getApplicationProtocol() = "h2",协议 TLSv1.3;服务器 getApplicationProtocol() = "h2"
    客户端 [http/1.1, h2]  服务器 [h2, http/1.1] → 客户端 getApplicationProtocol() = "h2",协议 TLSv1.3;服务器 getApplicationProtocol() = "h2"
    客户端 [h2, http/1.1]  服务器 [http/1.1, h2] → 客户端 getApplicationProtocol() = "http/1.1",协议 TLSv1.3;服务器 getApplicationProtocol() = "http/1.1"
  [TLSv1.2]
    客户端 [h2, http/1.1]  服务器 [http/1.1]     → 客户端 getApplicationProtocol() = "http/1.1",协议 TLSv1.2;服务器 getApplicationProtocol() = "http/1.1"
    客户端 [h2, http/1.1]  服务器 [h2, http/1.1] → 客户端 getApplicationProtocol() = "h2",协议 TLSv1.2;服务器 getApplicationProtocol() = "h2"
    客户端 [http/1.1, h2]  服务器 [h2, http/1.1] → 客户端 getApplicationProtocol() = "h2",协议 TLSv1.2;服务器 getApplicationProtocol() = "h2"
    客户端 [h2, http/1.1]  服务器 [http/1.1, h2] → 客户端 getApplicationProtocol() = "http/1.1",协议 TLSv1.2;服务器 getApplicationProtocol() = "http/1.1"

没有交集、没有设置与 selector

实测 · 本机执行输出
  客户端 [h2]               服务器 [http/1.1](没有交集)→ 客户端异常:SSLHandshakeException: (no_application_protocol) Received fatal alert: no_application_protocol;服务器端异常:SSLHandshakeException: (no_application_protocol) No matching application layer protocol values
  客户端没有设置 ALPN        服务器 [h2, http/1.1]          → 客户端 getApplicationProtocol() = "",协议 TLSv1.3;服务器 getApplicationProtocol() = ""
  客户端 [h2, http/1.1]     服务器没有设置 ALPN             → 客户端 getApplicationProtocol() = "",协议 TLSv1.3;服务器 getApplicationProtocol() = ""
  双方都没有设置 ALPN                                       → 客户端 getApplicationProtocol() = "",协议 TLSv1.3;服务器 getApplicationProtocol() = ""
  TLS 1.2,客户端 [h2] 服务器 [http/1.1](没有交集)       → 客户端异常:SSLHandshakeException: (no_application_protocol) Received fatal alert: no_application_protocol;服务器端异常:SSLHandshakeException: (no_application_protocol) No matching application layer protocol values
  服务器用 selector,偏爱客户端列表的最后一个        → 客户端 getApplicationProtocol() = "spdy/3",协议 TLSv1.3;服务器 getApplicationProtocol() = "spdy/3"
  服务器 selector 返回 null                              → 客户端异常:SSLHandshakeException: (no_application_protocol) Received fatal alert: no_application_protocol;服务器端异常:SSLHandshakeException: (no_application_protocol) No matching application layer protocol values
  服务器 selector 返回空字符串 ""                      → 客户端 getApplicationProtocol() = "",协议 TLSv1.3;服务器 getApplicationProtocol() = ""
  服务器 selector 返回客户端没有提供的 "foo"           → 客户端异常:SSLHandshakeException: (no_application_protocol) Received fatal alert: no_application_protocol;服务器端异常:SSLHandshakeException: (no_application_protocol) No matching application layer protocol values
  selector 拿到的客户端提供的列表 = [h2, http/1.1, acme-tls/1]

真实的 nginx

实测 · 本机执行输出
  nginx(http2 on;)     openssl s_client -alpn h2,http/1.1  → h2 
  nginx(http2 on;)     openssl s_client -alpn http/1.1,h2  → h2 
  nginx(http2 on;)     openssl s_client -alpn h2           → h2 
  nginx(http2 on;)     openssl s_client -alpn http/1.1     → http/1.1 
  nginx(http2 on;)     openssl s_client -alpn spdy/3.1     → 没有协商 ALPN alert no application protocol:ssl/record/rec_layer_s3.c:918:SSL alert number 120
  nginx(http2 on;)     openssl s_client -alpn acme-tls/1   → 没有协商 ALPN alert no application protocol:ssl/record/rec_layer_s3.c:918:SSL alert number 120
  nginx(没有开 http2)     openssl s_client -alpn h2,http/1.1  → http/1.1 
  nginx(没有开 http2)     openssl s_client -alpn http/1.1,h2  → http/1.1 
  nginx(没有开 http2)     openssl s_client -alpn h2           → 没有协商 ALPN alert no application protocol:ssl/record/rec_layer_s3.c:918:SSL alert number 120
  nginx(没有开 http2)     openssl s_client -alpn http/1.1     → http/1.1 
  nginx(没有开 http2)     openssl s_client -alpn spdy/3.1     → 没有协商 ALPN alert no application protocol:ssl/record/rec_layer_s3.c:918:SSL alert number 120
  nginx(没有开 http2)     openssl s_client -alpn acme-tls/1   → 没有协商 ALPN alert no application protocol:ssl/record/rec_layer_s3.c:918:SSL alert number 120
  nginx(http2 on;)     curl --http2 实际使用 HTTP/2,curl --http1.1 实际使用 HTTP/1.1
  nginx(没有开 http2)     curl --http2 实际使用 HTTP/1.1,curl --http1.1 实际使用 HTTP/1.1
  nginx(http2 on;):
  Java HttpClient(请求版本 HTTP_2)→ 响应版本 HTTP_2,body = ok
  Java HttpClient(请求版本 HTTP_1_1)→ 响应版本 HTTP_1_1,body = ok
  nginx(没有开 http2):
  Java HttpClient(请求版本 HTTP_2)→ 响应版本 HTTP_1_1,body = ok
  Java HttpClient(请求版本 HTTP_1_1)→ 响应版本 HTTP_1_1,body = ok

分步演示

ALPN
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / OpenJDK(Temurin 17.0.20、Homebrew 21.0.12 与 27,每篇注明实际用的版本);用 javac 编译、java 直接运行,需要第三方库时用 Maven 解析依赖;计时用 System.nanoTime,数字每次运行会有小幅抖动。JDK 21.0.12 SSLSocket;nginx 1.31.6、OpenSSL 3.6.4、curl 8.7.1;证书是本机自签名,服务全部在 127.0.0.1 的 9600+ 端口
    • 没有验证:SSLEngine 的 ALPN(Netty 使用的方式)、更多的应用层协议(如 acme-tls/1 的真实用途)、nginx 的 ssl_alpn 相关指令的其它配置、HTTP/3 的 ALPN(h3)、ALPN 在 TLS 1.2 重新协商(renegotiation)时的行为
    京ICP备2026067502号-1京公网安备11010502063487号