服务器自动识别客户端并切换证书,其核心通常并不依赖复杂的“识别”逻辑,而是巧妙利用了TLS协议本身的协商机制。
主要有三种实现思路,各有优劣:
方案一:基于SNI的证书选择(多域名场景)
Server Name Indication (SNI) 是TLS协议的扩展,允许客户端在握手时告诉服务器它要访问的域名。服务器根据这个信息,就能从众多证书中选出正确的那一个。
适用场景:同一台服务器上托管了多个不同的网站或服务(例如 `rsa.example.com` 和 `ecc.example.com`),每个域名使用不同的证书。
实现方式:在Nginx中,为每个域名配置独立的 `server` 块,并在其中指定对应的证书。
局限性:要求客户端(如浏览器)支持SNI。虽然现代浏览器都支持,但极少数的老旧客户端可能不支持。
方案二:基于TLS协商的自动证书选择(单域名双证书)
这是实现“单域名、双证书”最主流、最优雅的方法。服务器同时提供RSA和ECC(或SM2)两套证书,最终使用哪套由客户端和服务器共同协商决定。
核心原理:协商过程由密码套件(Cipher Suite) 驱动。服务器配置的密码套件列表中同时包含 `ECDHE-ECDSA`(对应ECC证书)和 `ECDHE-RSA`(对应RSA证书)。客户端在握手时,会从列表中选择一个自己支持的密码套件。如果客户端支持ECC,它就可能选择 `ECDHE-ECDSA` 套件,从而“触发”服务器使用ECC证书。
适用场景:希望用一个域名同时服务现代高性能客户端(使用ECC证书)和旧版客户端(使用RSA证书)。
Nginx配置关键点:
1. 环境要求:Nginx ≥ 1.11.0,OpenSSL ≥ 1.0.2。
2. 证书要求:准备两套独立的证书(RSA和ECC),它们的主体(Subject)和所有SAN(Subject Alternative Name)必须完全一致。
3. 配置写法:在同一个 `server` 块中,连续写入两组证书指令。
```nginx
server {
listen 443 ssl;
server_name example.com;
# RSA 证书(建议放在前面)
ssl_certificate /path/to/example.com.rsa.crt;
ssl_certificate_key /path/to/example.com.rsa.key;
# ECC 证书(紧接着RSA配置)
ssl_certificate /path/to/example.com.ecc.crt;
ssl_certificate_key /path/to/example.com.ecc.key;
# 协议和密码套件配置
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:...;
# ... 其他配置
}
```
4. 密码套件:`ssl_ciphers` 配置必须**同时包含** `ECDHE-ECDSA` 和 `ECDHE-RSA` 开头的套件。
方案三:基于Lua脚本的动态选择(高级定制)
如果前两种方案无法满足需求(例如需要根据客户端IP、TLS版本等其他信息做决策),可以使用Lua脚本在TLS握手阶段进行更细粒度的控制。
核心原理:在Nginx的 `ssl_certificate_by_lua_block` 阶段,通过Lua脚本分析客户端的 `ClientHello` 消息,获取其支持的签名算法等信息。
实现方式:例如,脚本检测到客户端支持ECDSA签名算法,则动态加载ECC证书;否则回退到RSA证书。这需要Nginx编译了 `ngx_lua_module` 模块。
方案对比总结
特性 方案一:基于SNI 方案二:TLS协商 方案三:Lua脚本
实现难度 低 中 高
灵活性 低(依赖域名) 中(依赖TLS协商) 高(可编程控制)
性能开销 无 无 极小
主要优点 配置简单,广泛支持 单域名,自动适配,无额外性能损耗 控制力强,可应对复杂逻辑
主要缺点 需要多域名或SNI支持 对证书和Nginx版本有要求 配置复杂,需额外模块
总结与建议
首选方案二(TLS协商):对于大多数“单域名双证书”(如RSA+ECC或国密+国际)的场景,这是最推荐的方式。它配置标准、性能最优,且对用户完全透明。
备选方案一(SNI):如果你的双证书分别对应完全不同的域名,这是最直接的选择。
最后考虑方案三(Lua脚本):只有当你的需求非常特殊,前两种方案都无法满足时,才考虑这种复杂的定制化方案。