You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.18 12:35:30