tls.requireandverifyclientcert 是服务端启用真 mtls 的唯一有效值,必须手动构造 http.server 并配置非空 clientcas 和正确 clientauth,客户端需正确加载证书链与私钥,grpc 配置独立且服务端客户端均需显式设置 tls 凭据。

tls.RequireAndVerifyClientCert 是服务端启用真 mTLS 的唯一有效值,不设它,就只是单向 TLS。
服务端必须显式设 ClientAuth: tls.RequireAndVerifyClientCert
Go 默认的 http.ListenAndServeTLS 会创建一个 tls.Config,但它的 ClientAuth 是 tls.NoClientCert,等于完全关闭双向流程。你传进去的客户端 CA 文件压根不会被用到。
常见错误配置:tls.VerifyClientCertIfGiven(允许无证书连接)或 tls.RequireAnyClientCert(只验格式,不验签发者),都不满足强制双向要求。
必须手动构造 http.Server,并在 TLSConfig 中明确写:
ClientAuth: tls.RequireAndVerifyClientCert-
ClientCAs必须是非空的*x509.CertPool,且已成功加载 PEM 内容(不能传路径字符串或空池) - 服务端自己的证书和私钥仍走
Certificates字段,与ClientCAs完全解耦 - 若用
net/http.Server,记得把完整TLSConfig挂到srv.TLSConfig上,再调srv.ListenAndServeTLS("", "")(空字符串表示从 config 读证书)
客户端证书加载失败的三个硬性条件
报错如 x509: certificate signed by unknown authority 或 remote error: tls: bad certificate,往往不是证书内容错,而是加载方式或上下文错。
-
client.crt必须包含完整链:leaf 证书在前,中间 CA 在后;root CA 单独交给RootCAs,不能混进client.crt - 私钥文件权限必须是
0600,否则 Go 静默忽略,无任何提示 -
http.Transport.TLSClientConfig.RootCAs必须加载服务端的 CA 根证书(即签发服务端证书的那个 PEM),不是服务端的公钥证书本身 -
gRPC客户端若写credentials.NewTLS(nil),等于创建空tls.Config,既不发证也不验证,彻底退化
为什么 http.Transport 在 mTLS 下默认不复用连接
因为 http.Transport 的连接复用哈希键包含整个 tls.Config 实例。而每个客户端证书不同,若每次请求都 new 一个带不同 Certificates 的 tls.Config,哈希值就总变,连接永远无法复用。
- 这不是 bug,是设计使然——TLS 层面身份已绑定到连接,复用意味着身份混用
- 解决办法只有两个:所有客户端共用同一套证书(适合网关后统一出口),此时可复用
http.Transport实例;或每个请求必须带独立证书时,禁用复用:Transport.MaxIdleConns = 0 - 别指望靠
ServerName或InsecureSkipVerify绕过这个问题——前者只影响证书校验,后者直接废掉全部安全机制
gRPC 的 mTLS 配置和 HTTP 不互通
gRPC 不复用 http.Server 的 TLS 配置,也不接受 http.ListenAndServeTLS 的参数。它的 mTLS 是独立路径,且服务端/客户端配置缺一不可。
- 服务端启动时必须用
grpc.Creds(credentials.NewTLS(tlsConfig)),其中tlsConfig同样要设ClientAuth: tls.RequireAndVerifyClientCert和ClientCAs - 客户端
Dial时必须用grpc.WithTransportCredentials(credentials.NewTLS(tlsConfig)),且tlsConfig必须含Certificates和RootCAs - 在
gRPC拦截器中,用peer.FromContext(ctx)断言peer.AuthInfo获取credentials.TLSInfo,再从State.VerifiedChains[0][0].Subject提取身份——注意CommonName不可靠,推荐用 SPIFFE ID 或自定义扩展字段
tls: failed to find any PEM data in certificate input。别等上线才发现连不上。











