本文目录导读:

在 PHP 中实现 mTLS(双向 TLS)绑定,主要涉及客户端证书验证和服务端证书验证两个方面,以下是完整的实现方案:
PHP 作为客户端(发起请求)
cURL 实现 mTLS
<?php
$ch = curl_init('https://example.com/api');
// 设置 CA 证书(验证服务端)
curl_setopt($ch, CURLOPT_CAINFO, '/path/to/ca-cert.pem');
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
// 客户端证书和私钥
curl_setopt($ch, CURLOPT_SSLCERT, '/path/to/client-cert.pem');
curl_setopt($ch, CURLOPT_SSLKEY, '/path/to/client-key.pem');
curl_setopt($ch, CURLOPT_SSLKEYPASSWD, 'your_key_password'); // 如果私钥有密码
// 如果使用 PKCS#12 格式
// curl_setopt($ch, CURLOPT_SSLCERT, '/path/to/client.p12');
// curl_setopt($ch, CURLOPT_SSLCERTPASSWD, 'p12_password');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
if (curl_errno($ch)) {
echo 'Error: ' . curl_error($ch);
}
curl_close($ch);
PHP Stream Context 实现
<?php
$context = stream_context_create([
'ssl' => [
'cafile' => '/path/to/ca-cert.pem',
'verify_peer' => true,
'verify_peer_name' => true,
'local_cert' => '/path/to/client-cert.pem',
'local_pk' => '/path/to/client-key.pem',
'passphrase' => 'your_key_password',
'allow_self_signed' => false
]
]);
$result = file_get_contents('https://example.com/api', false, $context);
PHP 作为服务端(接收请求并验证客户端)
Apache 配置示例
<VirtualHost *:443>
ServerName example.com
# HTTPS 配置
SSLEngine on
SSLCertificateFile /path/to/server-cert.pem
SSLCertificateKeyFile /path/to/server-key.pem
# 客户端证书验证
SSLVerifyClient require
SSLVerifyDepth 10
SSLCACertificateFile /path/to/client-ca-cert.pem
# 可选:将客户端证书信息传递给 PHP
SSLOptions +StdEnvVars
DocumentRoot /var/www/html
</VirtualHost>
Nginx 配置
server {
listen 443 ssl;
server_name example.com;
ssl_certificate /path/to/server-cert.pem;
ssl_certificate_key /path/to/server-key.pem;
ssl_client_certificate /path/to/client-ca-cert.pem;
ssl_verify_client on;
ssl_verify_depth 10;
location / {
proxy_pass http://php-fpm:9000;
proxy_set_header X-Client-Cert $ssl_client_raw_cert;
proxy_set_header X-Client-Verify $ssl_client_verify;
proxy_set_header X-Client-DN $ssl_client_s_dn;
}
}
PHP 获取客户端证书信息
<?php
// 获取客户端证书信息(Apache 环境)
if (isset($_SERVER['SSL_CLIENT_VERIFY'])) {
$verify = $_SERVER['SSL_CLIENT_VERIFY']; // SUCCESS, FAILED, NONE
if ($verify === 'SUCCESS') {
// 证书信息
$certDN = $_SERVER['SSL_CLIENT_S_DN'];
$certIssuer = $_SERVER['SSL_CLIENT_I_DN'];
$certSerial = $_SERVER['SSL_CLIENT_M_SERIAL'];
$certExpiry = $_SERVER['SSL_CLIENT_V_END'];
// 从证书 DN 中提取信息
$parts = explode(',', $certDN);
foreach ($parts as $part) {
list($key, $value) = explode('=', $part, 2);
$info[strtolower(trim($key))] = trim($value);
}
$commonName = $info['cn'] ?? '';
$email = $info['emailaddress'] ?? '';
// 可以基于证书信息做业务逻辑
if (!isAuthorized($commonName, $email)) {
http_response_code(403);
exit('Unauthorized');
}
}
}
// Nginx 环境
if (isset($_SERVER['HTTP_X_CLIENT_VERIFY'])) {
if ($_SERVER['HTTP_X_CLIENT_VERIFY'] === 'SUCCESS') {
$clientDN = $_SERVER['HTTP_X_CLIENT_DN'] ?? '';
// 处理证书信息
}
}
// 更复杂的证书解析(从原始证书)
if (isset($_SERVER['HTTP_X_CLIENT_CERT'])) {
$certPem = "-----BEGIN CERTIFICATE-----\n"
. chunk_split(base64_decode($_SERVER['HTTP_X_CLIENT_CERT']), 64, "\n")
. "-----END CERTIFICATE-----";
$certData = openssl_x509_parse($certPem);
if ($certData) {
$subject = $certData['subject'];
$commonName = $subject['CN'] ?? '';
$serialNumber = $certData['serialNumber'] ?? '';
$validFrom = $certData['validFrom_time_t'] ?? 0;
$validTo = $certData['validTo_time_t'] ?? 0;
}
}
// 自定义授权逻辑
function isAuthorized($cn, $email) {
// 检查证书是否在授权列表中
$authorizedCerts = [
'John Doe' => 'john@example.com',
'Jane Smith' => 'jane@example.com'
];
return isset($authorizedCerts[$cn]) && $authorizedCerts[$cn] === $email;
}
?>
完整的客户端证书生成脚本
#!/bin/bash
# 生成自签名 CA
openssl genrsa -out ca.key 2048
openssl req -x509 -new -nodes -key ca.key -sha256 -days 3650 -out ca.crt \
-subj "/C=US/ST=State/L=City/O=Organization/CN=My CA"
# 生成客户端证书
openssl genrsa -out client.key 2048
openssl req -new -key client.key -out client.csr \
-subj "/C=US/ST=State/L=City/O=Organization/CN=John Doe/emailAddress=john@example.com"
openssl x509 -req -in client.csr -CA ca.crt -CAkey ca.key -CAcreateserial \
-out client.crt -days 365 -sha256
# 生成 PKCS#12 格式(包含证书和私钥)
openssl pkcs12 -export -out client.p12 -inkey client.key -in client.crt -certfile ca.crt
PHP 框架集成示例(Laravel)
// app/Http/Middleware/VerifyClientCertificate.php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
class VerifyClientCertificate
{
public function handle(Request $request, Closure $next)
{
if (!$this->hasValidClientCertificate($request)) {
return response('Unauthorized', 403);
}
// 将证书信息存入请求属性
$request->attributes->add([
'client_cert' => $this->getClientCertificateData($request)
]);
return $next($request);
}
protected function hasValidClientCertificate($request)
{
// Nginx/Apache 传递的验证结果
return $request->server('SSL_CLIENT_VERIFY') === 'SUCCESS' ||
$request->header('X-Client-Verify') === 'SUCCESS';
}
protected function getClientCertificateData($request)
{
return [
'subject' => $request->server('SSL_CLIENT_S_DN', $request->header('X-Client-DN', '')),
'issuer' => $request->server('SSL_CLIENT_I_DN', ''),
'serial' => $request->server('SSL_CLIENT_M_SERIAL', ''),
];
}
}
mTLS 测试脚本
<?php
// test-mtls.php - 简单的 mTLS 测试
class MTLSClient {
private $certFile;
private $keyFile;
private $caFile;
public function __construct($cert, $key, $ca) {
$this->certFile = $cert;
$this->keyFile = $key;
$this->caFile = $ca;
}
public function request($url, $method = 'GET', $data = null) {
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_CAINFO => $this->caFile,
CURLOPT_SSLCERT => $this->certFile,
CURLOPT_SSLKEY => $this->keyFile,
CURLOPT_SSL_VERIFYPEER => true,
CURLOPT_SSL_VERIFYHOST => 2,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => $method,
CURLOPT_TIMEOUT => 30
]);
if ($data) {
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
}
$response = curl_exec($ch);
$error = curl_error($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
return [
'http_code' => $httpCode,
'response' => $response,
'error' => $error
];
}
}
// 使用示例
$client = new MTLSClient(
'/path/to/client.crt',
'/path/to/client.key',
'/path/to/ca.crt'
);
$result = $client->request('https://api.example.com/secure-endpoint', 'GET');
var_dump($result);
?>
注意事项
- 证书安全:私钥文件权限设为 600,确保只有 web 服务器用户可读
- 证书轮换:实施证书过期监控和自动更新机制
- 错误处理:适当处理各种 SSL/TLS 错误情况
- 性能优化:开启 SSL 会话缓存以提高性能
- 日志记录:记录证书验证失败和被拒绝的请求
通过这些配置和代码,可以全面实现 PHP 应用的 mTLS 绑定。