如何使用PKCS7标准签名完整XML消息(Feign客户端实现)
实现Feign客户端下的PKCS7 XML签名方案
1. 添加必要依赖
首先引入处理PKCS7签名的加密库及Feign核心依赖:
<!-- BouncyCastle 加密库 --> <dependency> <groupId>org.bouncycastle</groupId> <artifactId>bcprov-jdk15on</artifactId> <version>1.70</version> </dependency> <dependency> <groupId>org.bouncycastle</groupId> <artifactId>bcpkix-jdk15on</artifactId> <version>1.70</version> </dependency> <!-- Feign 核心依赖 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency>
2. 编写PKCS7签名工具类
实现带附件模式的PKCS7签名逻辑,将XML内容签名后转换为BASE64格式:
import org.bouncycastle.cms.CMSProcessableByteArray; import org.bouncycastle.cms.CMSSignedData; import org.bouncycastle.cms.CMSSignedDataGenerator; import org.bouncycastle.cms.CMSTypedData; import org.bouncycastle.cms.jcajce.JcaCertStore; import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoGeneratorBuilder; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.operator.OperatorCreationException; import java.io.ByteArrayInputStream; import java.nio.file.Files; import java.nio.file.Paths; import java.security.KeyStore; import java.security.PrivateKey; import java.security.Security; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Base64; import java.util.List; public class PKCS7Signer { static { Security.addProvider(new BouncyCastleProvider()); } // 从P12证书文件加载私钥与证书链 private static KeyStore loadKeyStore(String p12Path, String password) throws Exception { KeyStore keyStore = KeyStore.getInstance("PKCS12", "BC"); try (ByteArrayInputStream is = new ByteArrayInputStream(Files.readAllBytes(Paths.get(p12Path)))) { keyStore.load(is, password.toCharArray()); } return keyStore; } // 生成PKCS7签名并返回BASE64编码结果 public static String signXml(String xmlContent, String p12Path, String password, String certAlias) throws Exception { KeyStore keyStore = loadKeyStore(p12Path, password); PrivateKey privateKey = (PrivateKey) keyStore.getKey(certAlias, password.toCharArray()); X509Certificate cert = (X509Certificate) keyStore.getCertificate(certAlias); List<X509Certificate> certChain = new ArrayList<>(); certChain.add(cert); CMSSignedDataGenerator generator = new CMSSignedDataGenerator(); generator.addSignerInfoGenerator( new JcaSimpleSignerInfoGeneratorBuilder() .setProvider("BC") .build("SHA256withRSA", privateKey, cert) ); generator.addCertificates(new JcaCertStore(certChain)); // 带附件模式:签名包含原始XML内容(enveloping模式) // 若文档要求分离签名(detached),将generate第二个参数改为true,此时签名不含原始内容需单独发送 CMSTypedData data = new CMSProcessableByteArray(xmlContent.getBytes("UTF-8")); CMSSignedData signedData = generator.generate(data, false); return Base64.getEncoder().encodeToString(signedData.getEncoded()); } }
3. 在Feign客户端中集成签名逻辑
提供两种集成方式,根据接口要求选择:
方式1:通过RequestInterceptor自动签名
拦截XML类型的请求,自动完成签名并替换请求体:
import feign.RequestInterceptor; import feign.RequestTemplate; import org.springframework.stereotype.Component; @Component public class SignInterceptor implements RequestInterceptor { @Override public void apply(RequestTemplate template) { if (template.requestBody().asBytes() == null) { return; } List<String> contentType = template.headers().get("Content-Type"); if (contentType != null && contentType.get(0).contains("application/xml")) { try { String originalXml = new String(template.requestBody().asBytes(), "UTF-8"); // 调用签名工具生成BASE64签名 String signedContent = PKCS7Signer.signXml(originalXml, "/path/to/your/cert.p12", "cert-password", "cert-alias"); // 若接口要求将签名嵌入XML节点,需先解析XML插入签名再序列化 // 示例:解析XML插入<Signature>节点后重新生成XML字符串替换请求体 // 若接口要求直接发送签名后的BASE64内容,替换请求体 template.body(signedContent); } catch (Exception e) { throw new RuntimeException("XML签名失败", e); } } } }
方式2:在Feign接口方法中手动处理
先完成签名,再将签名后的数据传入Feign接口:
import feign.Headers; import feign.RequestLine; public interface YourServiceClient { @RequestLine("POST /api/target-endpoint") @Headers("Content-Type: application/xml") String sendSignedRequest(String signedContent); } // 调用示例 // String originalXml = "<your-xml-content>...</your-xml-content>"; // String signedXml = PKCS7Signer.signXml(originalXml, p12Path, password, certAlias); // yourServiceClient.sendSignedRequest(signedXml);
注意事项
- 证书加载优化:Spring Boot环境下建议用
ResourceLoader加载resources目录下的证书,避免硬编码路径。 - 签名模式确认:需与接口文档严格对齐,若为分离签名(detached),需同时发送原始XML和BASE64签名。
- 编码一致性:全程使用UTF-8编码,避免乱码导致签名验证失败。
- XML嵌入签名:若接口要求签名嵌入XML特定节点,需先解析XML插入签名内容,再重新序列化XML后执行签名(或先签名再插入,以文档要求为准)。
内容的提问来源于stack exchange,提问作者neoerol
相关产品推荐
相关产品推荐

