如何将客户端证书转发至X.509认证的Spring Boot后端(WSO2 APIM场景)
我来帮你拆解这个问题——你碰到的null cert chain和后续的Bad certificate错误,核心原因是WSO2网关没有正确接收并透传客户端的X.509证书到你的Spring Boot后端。结合你的多客户端场景,咱们一步步来解决:
先明确你的场景架构
你的需求是:客户端<->WSO2网关启用双向SSL,同时让WSO2仅把客户端的X.509证书转发给后端的Spring Boot服务(后端只验证客户端证书,不验证WSO2网关)。之前Postman直接调后端没问题,是因为Postman直接把客户端证书发给了后端;但接入WSO2后,网关没做好证书透传,才引发了SSL错误。
一、先解决"null cert chain"的根源:确保WSO2能拿到客户端证书
这个错误说明WSO2网关根本没收到客户端的证书,大概率是双向SSL配置没到位:
1. 用最新的deployment.toml配置双向SSL(别再改axis2.xml了)
WSO2现在推荐用repository/conf/deployment.toml做配置,避免老配置冲突。添加以下内容:
[transport.https.properties] sslProtocol = "TLSv1.2" enabled = true port = 8243 keyStoreFile = "${carbon.home}/repository/resources/security/wso2carbon.jks" keyStorePassword = "wso2carbon" trustStoreFile = "${carbon.home}/repository/resources/security/client-truststore.jks" trustStorePassword = "wso2carbon" needClientAuth = "true" # 强制客户端提供证书
2. 把客户端的CA证书导入WSO2信任库
WSO2要信任客户端的证书,必须把客户端证书的根CA导入到client-truststore.jks里(注意:不是后端服务的证书,后端证书是WSO2信任后端用的):
keytool -importcert -alias client-root-ca -file /path/to/client-ca.crt -keystore ${carbon.home}/repository/resources/security/client-truststore.jks -storepass wso2carbon
3. Postman调用WSO2时要带对证书
调用WSO2的API端点时,Postman里要导入客户端自己的证书(CRT+KEY+密码),而不是后端的证书!之前直接调后端用的是后端证书,现在调WSO2得换客户端证书。
二、解决"Bad certificate":确保证书能正确透传到后端
当WSO2能拿到客户端证书后,还需要把证书转发给后端。默认WSO2不会自动做这件事,得通过自定义中介实现:
1. 写一个自定义中介类提取并传递客户端证书
创建一个Java类,继承AbstractMediator,从SSL会话中提取客户端证书,然后设置到后端调用的上下文里:
import org.apache.synapse.MessageContext; import org.apache.synapse.mediators.AbstractMediator; import javax.net.ssl.SSLSession; import java.security.cert.X509Certificate; public class ClientCertForwardMediator extends AbstractMediator { @Override public boolean mediate(MessageContext context) { // 从WSO2的传输上下文里拿SSL会话 SSLSession sslSession = (SSLSession) context.getProperty("TRANSPORT_SSL_SESSION"); if (sslSession == null) { handleException("No SSL session found - client didn't send certificate?", context); return false; } try { // 获取客户端证书链 X509Certificate[] clientCerts = (X509Certificate[]) sslSession.getPeerCertificates(); if (clientCerts != null && clientCerts.length > 0) { // 把客户端证书存到上下文,供后端调用时使用 context.setProperty("CLIENT_X509_CERT", clientCerts[0]); // 额外:如果后端需要通过HTTP头接收证书,可以把证书转成PEM格式放到Header里 // String pemCert = "-----BEGIN CERTIFICATE-----\n" + Base64.getEncoder().encodeToString(clientCerts[0].getEncoded()) + "\n-----END CERTIFICATE-----"; // context.setProperty("HTTP_HEADER_X-CLIENT-CERT", pemCert); } } catch (Exception e) { handleException("Failed to extract client certificate", e, context); return false; } return true; } }
2. 部署自定义中介
把这个类打包成JAR,放到WSO2的repository/components/lib目录下,然后重启WSO2。
3. 在API的入流中添加这个中介
登录WSO2 APIM Publisher,编辑你的API:
- 进入「Message Mediation」→「In Flow」
- 添加一个「Class Mediator」,指定类名为你刚才写的
com.your.package.ClientCertForwardMediator(替换成你的实际包名)
4. 配置后端调用时使用客户端证书
最后,要让WSO2在调用后端时使用刚才提取的客户端证书。可以通过配置后端端点的SSL上下文,或者在中介流中动态设置SSL参数。如果你的后端只验证客户端证书,那WSO2需要用客户端证书来和后端建立SSL连接:
- 可以在
deployment.toml中为后端配置动态SSL,或者使用WSO2的端点属性来指定证书。
三、验证步骤
- 重启WSO2和Spring Boot后端
- Postman导入客户端证书,调用WSO2的API端点
- 查看WSO2的日志,确认客户端证书被成功提取
- 查看Spring Boot后端的日志,确认收到的是客户端的X.509证书,而不是WSO2网关的证书
这样应该就能解决你的问题了——核心是确保WSO2先正确接收客户端证书,再通过自定义中介把证书透传给后端。
内容的提问来源于stack exchange,提问作者Murat Yildiz

