双向 TLS(mTLS)认证:完整指南

544 阅读3分钟

适用于安全要求高的服务间通信,例如:

  • 服务 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