PHP 8.6 stream 的 TLS 会话复用与 0-RTT:三组 SSL 上下文选项的用法
来源:PHP: New Features - Manual,migration86.new-features.php 的 OpenSSL 章节。
PHP 8.6 给 stream 补了三块 TLS 能力:会话复用、外部 PSK、TLS 1.3 早数据(0-RTT)。它们都通过 stream_context_create() 的 ssl 上下文选项配置,可以按用途分成三组。
三组上下文选项
会话复用
会话复用(session resumption)允许客户端跨请求保存和恢复会话,也允许服务端自己实现会话存储、控制会话缓存行为。新增选项:
session_datasession_new_cbsession_cachesession_cache_sizesession_timeoutsession_id_contextsession_get_cbsession_remove_cbnum_tickets
客户端主要用 session_data 注入上次拿到的会话;session_new_cb 用来接收新建的会话。服务端方向是 session_get_cb、session_new_cb、session_remove_cb 三个回调,加上 session_cache、session_cache_size、session_timeout、session_id_context、num_tickets 控制缓存与票据行为。
外部 PSK
外部 PSK 用两个选项:
psk_client_cbpsk_server_cb
分别用于设置和接收 pre-shared key。适合已经有带外密钥分发渠道的服务之间连接,不依赖证书链。
早数据(0-RTT)
客户端用 early_data 选项发送早数据;服务端用 max_early_data 决定接受量,用 early_data_cb 接收数据。结果不通过返回值,而是写在 stream_get_meta_data() 返回数组的 crypto 条目下,键为 early_data,取值是 accepted、rejected、not_sent 三者之一。
客户端:反复调第三方 HTTPS
支付网关、内部服务这类需要高频调用的接口,每次完整握手开销不小。把会话存下来,下次连接时回填:
$session = $cache->get('tls:pay.example.com'); // 上次保存的会话
$ctx = stream_context_create([
'ssl' => [
'session_data' => $session,
'session_new_cb' => 'tls_save_session', // 回调签名以手册为准
'early_data' => true, // 开启 0-RTT 发送
],
]);
$fp = stream_socket_client(
'tls://pay.example.com:443',
$errno, $errstr, 5,
STREAM_CLIENT_CONNECT, $ctx
);
$meta = stream_get_meta_data($fp);
$early = $meta['crypto']['early_data'] ?? null; // accepted / rejected / not_sent
early_data 的结果要看,不能假定早数据一定被服务端接受:rejected 时数据会走正常握手路径重发,not_sent 说明压根没发出去。
服务端:存储要自己实现
服务端拿不到现成的会话池,session_get_cb、session_new_cb、session_remove_cb 都要自己接上 Redis、APCu 之类的存储:
$ctx = stream_context_create([
'ssl' => [
'session_get_cb' => 'tls_load_session',
'session_new_cb' => 'tls_store_session',
'session_remove_cb' => 'tls_drop_session',
'session_timeout' => 300,
'max_early_data' => 4096,
'early_data_cb' => 'tls_handle_early_data',
],
]);
$server = stream_socket_server(
'tls://0.0.0.0:443',
$errno, $errstr,
STREAM_SERVER_BIND | STREAM_SERVER_LISTEN,
$ctx
);
sid_context 相关选项 session_id_context 用来把会话绑定到具体服务身份,多站点共用一套缓存时要设,避免会话被跨站取到。
两个必须注意的风险
0-RTT 数据可以被重放。 早数据在握手完成前就被服务端处理,攻击者可以原样重发。因此 early_data 这条路径上只能放幂等操作,查询、读接口可以;支付下单、扣款、写状态一律不能走早数据,要等握手完成后的正常请求。
PSK 与会话缓存都有生命周期。 session_timeout、session_cache_size、num_tickets 决定缓存里的条目能活多久、能存多少;过期条目要能被 session_remove_cb 清掉,否则缓存只增不减。并发场景下,session_new_cb 的写入要考虑多个 worker 同时写同一份存储的竞争,用带原子性的写入或按会话 id 分键。
同版本其它 stream 相关改动
curl_getinfo增加size_delivered,需 libcurl 8.20.0+。CURLOPT_SEEKFUNCTION支持请求体重放。filter.max_filter_count限制php://filter链长度。- 新的 Stream 错误 API:
StreamError/StreamException、stream_last_errors,以及error_mode、error_store、error_handler选项。