适用于安全要求高的服务间通信,例如:
- 服务 A 和服务 B 都要验证对方的身份
- 双方都持有由同一个 CA 签发的“服务证书”
- 双方都配置 ssl_client_certificate,验证对方证书是否可信
📃 完整步骤概述
-
自动化脚本,一键生成证书并启动服务端和客户端, Root CA 证书。
-
配置 Go 服务端,启用双向 TLS(mTLS)验证。
-
配置 Python 客户端,提供客户端证书并进行双向验证。
🛠 自动化生成证书脚本(generate_cert.sh)
#!/bin/bash
set -e
CERT_DIR="certs"
DAYS_VALID=3650 # 有效期 10 年
mkdir -p "$CERT_DIR"
echo "🔐 生成 Root CA 私钥和自签名证书..."
openssl genpkey -algorithm RSA -out "$CERT_DIR/rootCA.key" -pkeyopt rsa_keygen_bits:4096
openssl req -x509 -new -nodes -key "$CERT_DIR/rootCA.key" -sha256 -days "$DAYS_VALID" \
-out "$CERT_DIR/rootCA.pem" -subj "/C=CN/ST=Test/L=Test/O=MyOrg/CN=MyRootCA"
echo "📄 生成服务端私钥和 CSR..."
openssl genpkey -algorithm RSA -out "$CERT_DIR/server.key" -pkeyopt rsa_keygen_bits:2048
openssl req -new -key "$CERT_DIR/server.key" -out "$CERT_DIR/server.csr" -subj "/C=CN/ST=Test/L=Test/O=MyOrg/CN=localhost"
echo "✅ 使用 CA 签发服务端证书(含 SAN)..."
cat > "$CERT_DIR/server_ext.cnf" <<EOF
authorityKeyIdentifier=keyid,issuer
basicConstraints=CA:FALSE
keyUsage = digitalSignature, keyEncipherment
subjectAltName = @alt_names
[alt_names]
DNS.1 = localhost
IP.1 = 127.0.0.1
EOF
openssl x509 -req -in "$CERT_DIR/server.csr" -CA "$CERT_DIR/rootCA.pem" -CAkey "$CERT_DIR/rootCA.key" \
-CAcreateserial -out "$CERT_DIR/server.crt" -days "$DAYS_VALID" -sha256 -extfile "$CERT_DIR/server_ext.cnf"
echo "📄 生成客户端私钥和 CSR..."
openssl genpkey -algorithm RSA -out "$CERT_DIR/client.key" -pkeyopt rsa_keygen_bits:2048
openssl req -new -key "$CERT_DIR/client.key" -out "$CERT_DIR/client.csr" -subj "/C=CN/ST=Test/L=Test/O=MyOrg/CN=client"
echo "✅ 使用 CA 签发客户端证书..."
openssl x509 -req -in "$CERT_DIR/client.csr" -CA "$CERT_DIR/rootCA.pem" -CAkey "$CERT_DIR/rootCA.key" \
-CAcreateserial -out "$CERT_DIR/client.crt" -days "$DAYS_VALID" -sha256
echo "🎉 所有证书已生成在 $CERT_DIR:"
ls -l "$CERT_DIR"
🎯 Go 服务端(启用双向验证)
package main
import (
"crypto/tls"
"fmt"
"log"
"net/http"
)
func helloHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Hello from Go HTTPS server with mTLS!")
}
func main() {
// 加载证书和密钥
serverCert, err := tls.LoadX509KeyPair("certs/server.crt", "certs/server.key")
if err != nil {
log.Fatalf("Failed to load server certificates: %v", err)
}
// 加载客户端证书
clientCA := "certs/rootCA.pem"
caCert, err := os.ReadFile(clientCA)
if err != nil {
log.Fatalf("Failed to load CA certificate: %v", err)
}
// 设置客户端验证(mTLS)
clientCertPool := x509.NewCertPool()
clientCertPool.AppendCertsFromPEM(caCert)
// 配置 HTTPS 服务器
server := &http.Server{
Addr: ":8443",
TLSConfig: &tls.Config{
Certificates: []tls.Certificate{serverCert},
ClientCAs: clientCertPool,
ClientAuth: tls.RequireAndVerifyClientCert, // 启用双向认证
},
Handler: http.HandlerFunc(helloHandler),
}
// 启动 HTTPS 服务
log.Println("Starting HTTPS server with mTLS at https://localhost:8443")
err = server.ListenAndServeTLS("certs/server.crt", "certs/server.key")
if err != nil {
log.Fatalf("Server failed: %v", err)
}
}
说明:
ClientAuth: tls.RequireAndVerifyClientCert:要求客户端提供证书并验证。
clientCertPool:为服务端加载客户端的 CA 证书,以便验证客户端证书
🎯 Python 客户端(启用双向验证
import asyncio
import aiohttp
import ssl
async def fetch():
ssl_context = ssl.create_default_context(cafile='certs/rootCA.pem')
# 加载客户端证书和私钥
ssl_context.load_cert_chain(certfile='certs/client.crt', keyfile='certs/client.key')
# 发送请求到服务端
async with aiohttp.ClientSession() as session:
async with session.get('https://localhost:8443', ssl=ssl_context) as resp:
text = await resp.text()
print(f"[{resp.status}] {text}")
if __name__ == '__main__':
asyncio.run(fetch())
说明:
ClientAuth: tls.RequireAndVerifyClientCert:要求客户端提供证书并验证。
clientCertPool:为服务端加载客户端的 CA 证书,以便验证客户端证书
附: 🎯 Nginx 服务的配置
server {
listen 443 ssl;
server_name your.domain.com;
ssl_certificate /etc/nginx/ssl/cert/server.crt;
ssl_certificate_key /etc/nginx/ssl/cert/server.key;
# 客户端证书验证(双向认证)
ssl_client_certificate /etc/nginx/ssl/rootCA.pem;
ssl_verify_client on;
# 安全配置(可选优化)
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
location / {
return 200 'mTLS success.\n';
add_header Content-Type text/plain;
}
}
curl验证
curl https://localhost:8443 --cert certs/client.crt --key certs/client.key --cacert certs/rootCA.pem
可选:浏览器访问
将 certs/rootCA.pem 导入浏览器受信任根证书列表 将 certs/client.crt 和 certs/client.key 转成 .p12 后导入浏览器:
openssl pkcs12 -export -in certs/client.crt -inkey certs/client.key -out client.p12