HTTPS 双向证书场景,如何在 JMeter 与 Locust 中配置并验证握手耗时
解读
国内金融、政务、IoT 等系统普遍采用“双向 TLS(mTLS)”做身份强校验,面试时考官不仅想看“能不能跑通”,更关注“如何量化握手成本、如何隔离握手耗时、如何在高并发下验证证书链与 OCSP 对性能的影响”。回答必须体现:
- 证书链完整且符合国密/国际双栈要求;
- 握手耗时指标精确到 TCP 完成后的第一个 ApplicationData 之前;
- 工具配置与代码级埋点并重,能落地到持续集成流水线;
- 压测结果可回溯,满足国内等保、央行 237 号文对“关键页面可溯源”的要求。
知识点
- mTLS 握手阶段:ClientHello → ServerHello → ServerCertificate → CertificateRequest → ServerHelloDone → ClientCertificate → ClientKeyExchange → CertificateVerify → ChangeCipherSpec → Finished → ApplicationData。
- 耗时定义:tcp.connect.time + ssl.handshake.time;JMeter 5.5 起通过 JMeter Property
https.default.protocol=TLSv1.3与jsse.enableSNIExtension=true控制;Locust 侧依赖 gevent 的ssl.SSLContext与time_to_first_byte差值计算。 - 国密双证书:签名证书 + 加密证书,需在 JDK 装入 bcprov-jdk18on、加载 SM2/SM3/SM4 算法提供者;JMeter 通过自定义 Sampler 调用 GMJSSE 的
GMSSLContext,Locust 通过ssl.PROTOCOL_TLS加载gmssl库。 - 证书固定:JMeter 的
Keystore Configuration元件支持 PKCS12 与 JKS,但默认不校验 CRL/OCSP,需打开系统属性com.sun.net.ssl.checkRevocation=true并在<JAVA_HOME>/lib/security/java.security中配置ocsp.enable=true。 - 耗时隔离:JMeter 用
Connect Time与SSL Handshake Time两个独立字段写入 jtl,Granfana 插件可直接绘图;Locust 需在on_start里复写HttpSession的send方法,在socket.do_handshake()前后打时间戳。 - 高并发下证书开销:ECC 256 位单核 QPS 损耗约 8%,RSA 2048 约 18%;国密 SM2 单核损耗约 12%,但硬件加速卡(海光、兆芯)可把损耗压到 3% 以内。
- 等保测评要求:测试报告需包含“证书链深度”“吊销状态”“密钥强度”“随机数熵值”四项截图,JMeter 可打开
-Djavax.net.debug=ssl:handshake重定向到日志,Locust 用ssl.SSLSocket.shared_ciphers()打印协商套件。
答案
一、JMeter 侧
- 准备客户端证书:
a. 导出用户证书+私钥到 user.p12,指定别名 client,密码 secret;
b. 把 CA 证书导入 JMeter 所在 JDK 的 truststore(cacerts),避免 UNKNOWN_CA 告警。 - 配置元件:
a. 测试计划→添加“Keystore Configuration”,路径指向 user.p12,密码 secret,别名变量 ${certAlias};
b. HTTP Request Defaults→Protocol 选 https,端口 443;
c. 在 system.properties 追加:
https.use.cached.ssl.context=false
https.default.protocol=TLSv1.3
javax.net.ssl.keyStore=user.p12
javax.net.ssl.keyStorePassword=secret
javax.net.ssl.keyStoreType=PKCS12
com.sun.net.ssl.checkRevocation=true
d. 如需国密,把https.default.protocol=GMSSLv1.1并替换 JSSE 提供者为 GMJSSE。 - 提取握手耗时:
a. 修改 user.properties:
sampleresult.timestamp.start=true
sampleresult.useNanoTime=true
b. 结果树中自动输出ConnectTime与SSLHandshakeTime;
c. 非 GUI 运行追加-Jjmeter.save.saveservice.ssl=true,jtl 文件会多两列,可直接喂给 Grafana。 - 验证:
a. 单线程跑一次,查看 jtl 中SSLHandshakeTime应≈TCP 建立后 2-RTT(TLS1.3)或 4-RTT(TLS1.2);
b. 开 200 线程阶梯加载,观察SSLHandshakeTime99 线突增即证书链或 OCSP 阻塞,需引入 stapling 或本地缓存。
二、Locust 侧
- 准备:
a. 把客户端证书转为 PEM 格式:user.pem + key.pem;
b. 安装geventhttpclient>=2.0与cryptography>=41,国密再加gmssl>=3.2.0。 - 代码模板:
import ssl, time, locust
from geventhttpclient import HTTPClient
from geventhttpclient.url import URL
class MutualTLSLocust(locust.HttpUser):
abstract = True
def __init__(self, *args, **kw):
super().__init__(*args, **kw)
ctx = ssl.create_default_context(ssl.Purpose.SERVER_AUTH)
ctx.load_cert_chain('user.pem', 'key.pem')
ctx.check_hostname = False # 如域名与证书 CN 不一致
ctx.verify_mode = ssl.CERT_REQUIRED
ctx.load_verify_locations('ca.pem')
self.client = HTTPClient.from_url(URL(self.host), ssl_context=ctx, concurrency=200)
class ApiUser(MutualTLSLocust):
@locust.task
def t(self):
t0 = time.perf_counter()
resp = self.client.get('/api')
t1 = time.perf_counter()
# 首次连接会触发 handshake,后续复用
locust.events.request.fire(
request_type='mTLS',
name='handshake' if resp._connection.sock else 'reuse',
response_time=int((t1-t0)*1000),
response_length=len(resp.content)
)
- 隔离首次握手:
a. 在on_start里主动预热:
self.client.get('/health')
用events.request把第一次耗时单独标记为ssl_handshake;
b. 并发场景下,Locust 的FastHttpUser默认连接池复用,如需强制全握手,把ctx.options |= ssl.OP_NO_TICKET并设置client.connection_pool_size = 0。 - 校验:
a. 本地抓包:tshark 过滤ssl.handshake.type == 1 and ip.src == 客户端IP,看 ClientHello 到 ChangeCipherSpec 时间差;
b. 对比 Locust 日志与抓包,误差应 <5 ms,确保脚本级指标可信;
c. 输出到 InfluxDB,配置 Grafana 面板:维度为certificate_type(RSA/SM2)、concurrency,指标为p99_ssl_handshake。
三、通过准则
- 单线程握手耗时:TLS1.3+ECC ≤ 90 ms,TLS1.2+RSA ≤ 250 ms,SM2 ≤ 110 ms;
- 2000 并发、持续 5 min,握手失败率 0%,99 线涨幅不超过基线 30%;
- 服务器端 CPU 增加 ≤ 5 个百分点,网络包重传率 ≤ 0.1%;
- 报告含证书链深度、OCSP 状态、协商套件、随机数熵值截图,满足央行 237 号文与等保 2.0 测评要求。
拓展思考
- 硬件加速:海光 C86 板卡对 SM2 签名可达 18 k ops/core,JMeter 通过 JNI 调用
libhsm.so,把SSLContext的私钥操作 offload 到卡内,可将握手 QPS 提升 4.5 倍;面试可提“如何在 JMeter 自定义 Sampler 里调用 JNI”。 - 会话复用 vs 全握手:在微服务网格中,Sidecar 默认启用 Session Ticket,若压测目标为“新建连接风暴”,需关闭 ticket 并随机化 ClientHello 填充,防止网关聚合欺骗结果。
- 0-RTT 风险:TLS1.3 0-RTT 存在重放攻击,金融交易类系统禁止开启,压测时可故意重放同一 Early-Data 观察服务器是否拒绝,验证安全策略。
- 证书热轮换:K8s 场景下,Cert-Manager 可做到 30 秒级滚动更新,压测脚本需支持动态加载新证书而不重启线程,可在 Locust 中监听文件事件,调用
ctx.load_cert_chain()热更新,考查候选人对“长连接无损轮换”的理解。 - 合规审计:等保 2.0 要求“关键操作可追溯到人”,可把 JMeter 的
SampleEvent写入区块链存证平台(如蚂蚁链 BaaS),每条握手记录带上证书指纹与操作员工号,面试中提及即可体现“性能+合规”双重意识。