VirtualHost级别无法按域名精确启用双向认证,因TLS握手发生在Host头解析前,SSLVerifyClient指令作用于IP:Port绑定而非域名;可行方案包括统一设optional_no_ca后用Require expr按HTTP_HOST和SSL_CLIENT_VERIFY动态控制,或为双向站点分配独立IP/端口并直接设require。
虚拟主机本身不支持直接配置
实现双向证书认证(mtls),因为该指令是 apache 的全局或服务器级(
)指令,但它的生效逻辑依赖于底层
ssl
/tls 握手阶段——而握手发生在 host 头解析之前,此时 apache 还不知道请求要路由到哪个虚拟主机。
为什么 VirtualHost 级别无法真正“按站点”启用双向认证
HTTPS 连接建立时,客户端先完成 TLS 握手(含证书交换和验证),之后才发送 HTTP 请求(含
头)。Apache 在握手阶段就必须决定是否要求客户端证书,而此时它尚未进入虚拟主机匹配流程。因此:
–
若写在某个
块中,实际作用于该 IP:Port 绑定的所有连接;
– 若同一 IP:Port 托管多个 HTTPS 虚拟主机(SNI 场景),所有站点都会触发客户端证书验证,无法仅对某一个开启;
– 即使使用 SNI,TLS 层的证书验证策略仍由监听端口的主配置决定,不能按域名动态切换。
可行的替代方案:按域名实现逻辑上的“特定站点”双向认证
虽然无法在 TLS 层精确按域名开关验证,但可通过组合配置,在应用层达成等效效果:
统一开启客户端证书验证 + 条件跳过
:在
或
上下文中设置
(不强制校验 CA,只收证书),再用
或
结合
和
做运行时判断。例如:
这样,非目标域名可直通,目标域名则检查证书有效性及 DN 字段,未提供或验证失败即返回 403。
更干净的做法:为双向认证站点分配独立 IP 或端口
若条件允许,这是最符合语义且无歧义的方式:
给需双向认证的站点绑定专用 IP(如
),其他站点用另一 IP(如
);
在对应
中直接配置:
客户端必须提供被该 CA 签发的有效证书才能完成握手,天然隔离于其他站点。
注意事项与调试要点
– 确保启用
和
;
– 使用
时,务必配合
才能在表达式中读取
变量;
– 浏览器访问会弹出证书选择框,API 调用建议用 curl 指定
;
– 日志中开启
可查看握手阶段证书验证详情(注意生产环境避免 info 级别日志泄露敏感信息)。
sslverifyclientHostSSLVerifyClient requireSSLVerifyClient optional_no_caSSLRequireRequire expr%{SSL_CLIENT_VERIFY}%{HTTP_HOST}SSLVerifyClient optional_no_ca
SSLVerifyDepth 1
# 仅对 api.example.com 强制验证通过的客户端证书
Require expr %{HTTP_HOST} != 'api.example.com' || \
(%{SSL_CLIENT_VERIFY} == 'SUCCESS' && %{SSL_CLIENT_S_DN_CN} =~ /CN=trusted-api-client/)
192.168.1.100:443192.168.1.101:443SSLVerifyClient requireSSLVerifyDepth 2SSLCACertificateFile /path/to/ca.crtmod_sslmod_authz_coreSSLVerifyClient optional_no_caSSLOptions +StdEnvVars%{SSL_CLIENT_*}--cert client.pem --key client.key --cacert ca.crtSSLLogLevel info