laravel 本身不原生支持 mtls,必须依赖前置反向代理(如 nginx)完成客户端证书验证与 tls 终止,再以 http 透传可信身份标识(如 x-client-cert-sha256)给 laravel;php 层无法安全干预 tls 握手阶段,直接解析 ssl_client_cert 不可靠且易被伪造。

直接说结论:Laravel 本身不原生支持 mTLS,但可以作为下游微服务安全接入 mTLS 通信链路——关键不在 Laravel 框架层做证书验证,而是在它前面的反向代理(如 Nginx、Envoy)完成客户端证书校验与 TLS 终止,再以 HTTP 转发请求给 Laravel 应用,并透传可信身份信息(如 X-Client-Cert-SHA256 或 X-User-DN)。
为什么不能在 Laravel 中直接做 mTLS 客户端认证?
Laravel 运行于 PHP-FPM 或 Swoole 等服务器环境,其底层依赖的是 Web 服务器(如 Nginx/Apache)或运行时(如 Swoole 的 HTTP server),而这些组件本身不提供完整的 TLS 握手控制能力,尤其无法在应用层主动发起或验证双向证书交换。PHP 的 stream_socket_enable_crypto() 或 openssl_x509_parse() 只能解析已建立连接后的证书,但无法干预 TLS 握手阶段的 client certificate request 和 verify 流程。
强行在 Laravel 中解析 $_SERVER['SSL_CLIENT_CERT'] 是危险且不可靠的:
- 该变量仅在 Apache + mod_ssl 配置了
SSLVerifyClient optional_no_ca时才存在,Nginx 默认不传递; - 即使存在,也无法验证证书签名链、CRL/OCSP 状态、有效期或 CN/SAN 匹配,容易被伪造;
- PHP-FPM 与 Web 服务器间是 FastCGI 协议,证书上下文极易丢失或被篡改。
Nginx 作为 mTLS 终止点的最小可行配置
这是最常见也最稳妥的做法:把证书验证交给 Nginx,Laravel 只处理已清洗过的请求。
你需要在 Nginx 配置中明确启用 client cert 验证,并只允许特定 CA 签发的证书通过:
upstream laravel_backend {
server 127.0.0.1:8000;
}
<p>server {
listen 443 ssl http2;
server_name api.example.com;</p><pre class="brush:php;toolbar:false;"># 服务器证书(供客户端验证)
ssl_certificate /etc/nginx/ssl/server.crt;
ssl_certificate_key /etc/nginx/ssl/server.key;
# 启用 mTLS:要求客户端提供证书
ssl_client_certificate /etc/nginx/ssl/ca.crt; # 根 CA 公钥,用于验证客户端证书链
ssl_verify_client on; # 必须提供且有效,否则 495 错误
# 可选:只接受特定 OU 或 CN 的证书(增强控制)
ssl_verify_depth 2;
location / {
# 将客户端证书主题 DN 透传给 Laravel
proxy_set_header X-Client-DN $ssl_client_s_dn;
# 或哈希摘要(更安全,防 DN 注入)
proxy_set_header X-Client-Cert-SHA256 $ssl_client_fingerprint;
proxy_pass http://laravel_backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}}
注意:$ssl_client_fingerprint 是 Nginx 1.19+ 提供的变量,值为客户端证书 SHA256 摘要,稳定且不可伪造,比解析 DN 更可靠。
Laravel 如何安全使用透传的证书标识?
收到 X-Client-Cert-SHA256 后,Laravel 不应直接信任它——必须查表比对预注册的服务证书指纹,才能确认调用方身份。
- 维护一张
service_certificates表,字段含service_name、fingerprint、is_active; - 在中间件中读取
request()->header('X-Client-Cert-SHA256'),查询数据库匹配; - 匹配失败则返回
401 Unauthorized,不进入业务逻辑; - 匹配成功后,可将
service_name注入 request 属性,供后续授权逻辑使用(例如限制某服务只能调用/v1/orders/notify); - 切勿用该指纹做用户身份认证(它代表服务,不是人)。
示例中间件片段:
public function handle(Request $request, Closure $next)
{
$fingerprint = $request->header('X-Client-Cert-SHA256');
if (!$fingerprint) {
abort(401, 'mTLS certificate required');
}
<pre class="brush:php;toolbar:false;">$service = ServiceCertificate::where('fingerprint', $fingerprint)
->where('is_active', true)
->first();
if (!$service) {
abort(401, 'Unknown or revoked service certificate');
}
$request->attributes->set('invoking_service', $service->service_name);
return $next($request);}
CA 管理与证书轮换最容易被忽略的点
很多团队卡在证书过期或吊销后服务中断,根本原因是缺乏自动化与显式生命周期管理:
- 每个微服务应使用独立子 CA 签发证书(而非共用根 CA),便于按服务粒度吊销;
- 证书有效期建议设为 90 天以内,强制定期轮换;
- 根 CA 私钥必须离线保存,子 CA 私钥应加密存储并限制访问权限;
- Laravel 应用本身不需要私钥,只需信任的
ca.crt(用于验证上游网关或外部服务的证书); - 不要把 CA 证书硬编码进 Docker 镜像——应通过 Kubernetes
Secret或 HashiCorp Vault 动态注入。
真正难的不是第一次配通 mTLS,而是让整套证书体系可持续运转。一旦某个服务证书过期,整个调用链就断,而错误日志往往只显示 “connection reset” 或 “handshake failed”,不会明说是谁的证书错了。











