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

生成SAMLResponse时签名计算失败及公钥流异常求助

SAMLResponse生成失败,签名计算报错排查

无法生成SAMLResponse,收到以下错误提示:

  • Unable to compute signature, Signature XMLObject does not have the XMLSignature created during marshalling
  • XMLObject does not have an XMLSignature instance, unable to compute signature

尝试打印公钥输入流的inStream.read()结果为inStream: -1,但公钥文件并非空文件,不确定该现象是否为问题根源。

错误日志

{ role=ADMIN } - Unable to compute signature, Signature XMLObject does not have the XMLSignature created during marshalling
{ role=ADMIN } - XMLObject does not have an XMLSignature instance, unable to compute signature
{ role=ADMIN } - XMLObject does not have an XMLSignature instance, unable to compute signature
{ role=ADMIN } - org.opensaml.xml.signature.SignatureException: XMLObject does not have an XMLSignature instance, unable to compute signature
{ role=ADMIN } - Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.NullPointerException] with root cause
java.lang.NullPointerException: null // at the mentioned error point

相关代码

package HIE.utils;


public class SSOSAMLResponse1 {

    public Response createSAMLResponse(final String subjectId, final HashMap<String, List<String>> attributes, String issuer) {
        try {
            DefaultBootstrap.bootstrap();
            DateTime datetime = new DateTime(Utilities.getCurrentTimeStamp());

            Issuer responseIssuer = null;
            Issuer assertionIssuer = null;
            Subject subject = null;
            AttributeStatement attributeStatement = null;

            Status status = createStatus();

            if (!Utilities.isNull(issuer)) {
                responseIssuer = createIssuer(issuer);
                assertionIssuer = createIssuer(issuer);
            }

            if (!Utilities.isNull(subjectId)) {
                subject = createSubject(subjectId);
            }

            if (!Utilities.isNull(attributes) && attributes.size() != 0) {
                attributeStatement = createAttributeStatement(attributes);
            }

            Conditions conditions = createConditions(datetime);

            AuthnStatement authnStatement = createAuthnStatement(datetime);

            Assertion assertion = createAssertion(datetime, subject, assertionIssuer, authnStatement, attributeStatement, conditions);

            Signature signature = createSignature();
            assertion.setSignature(signature);
            Signer.signObject(signature); // point of error

            return createResponse(datetime, responseIssuer, status, assertion);

        } catch(Throwable t) {
            log.warn(t.getMessage());
            log.warn(t.getLocalizedMessage());
            log.warn(t.toString());
            t.printStackTrace();
            return null;
        }
    }

    // skipping other defined functions

    private Signature createSignature() throws Throwable {
        String absolutePath = "C:/Users/USER/Desktop/office-projects/medicalservice/src/main/resources/sso/";
        String privateKeyLocation = absolutePath + "x509_priv.pem";
        String publicKeyLocation =  absolutePath + "x509_cert.crt";

        SignatureBuilder builder = new SignatureBuilder();
        Signature signature = builder.buildObject();
        Credential credential = getSigningCredential(publicKeyLocation, privateKeyLocation);
        signature.setSigningCredential(credential);
        signature.setSignatureAlgorithm(SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA1);
        signature.setCanonicalizationAlgorithm(SignatureConstants.ALGO_ID_C14N_EXCL_OMIT_COMMENTS);

        return signature;
    }

    public Credential getSigningCredential(String publicKeyLocation, String privateKeyLocation) throws Throwable {
        // create public key (cert) portion of credential
        InputStream inStream = new FileInputStream(publicKeyLocation);
        CertificateFactory cf = CertificateFactory.getInstance("X.509");
        X509Certificate x509Certificate = (X509Certificate) cf.generateCertificate(inStream);
        log.info("inStream: {}", inStream.read());
        inStream.close();

        // create private key
        File file = new File(privateKeyLocation);
        String key = new String(Files.readAllBytes(file.toPath()), Charset.defaultCharset());

        String privateKeyPEM = key
                .replace("-----BEGIN RSA PRIVATE KEY-----", "")
                .replaceAll(System.lineSeparator(), "")
                .replace("-----END RSA PRIVATE KEY-----", "");

        log.info("privateKeyPEM {}", privateKeyPEM);

        byte[] encoded = Base64.decodeBase64(privateKeyPEM);

        PKCS8EncodedKeySpec kSpec = new PKCS8EncodedKeySpec(encoded);
        KeyFactory kf = KeyFactory.getInstance("RSA");
        PrivateKey privateKey = kf.generatePrivate(kSpec);

        // create credential and initialize
        BasicX509Credential credential = new BasicX509Credential();
        credential.setEntityCertificate(x509Certificate);
        credential.setPrivateKey(privateKey);

        return credential;
    }

}

问题分析与修复方案

1. 公钥流读取-1的真相

inStream.read()返回-1是正常行为:cf.generateCertificate(inStream)已经将输入流的所有数据读取完毕用于解析证书,后续读取自然返回-1,这个不是报错根源,建议删除该日志避免混淆。

2. 签名失败的核心原因

OpenSAML签名依赖XML编组(Marshall)生成的XML上下文,直接对未编组的Assertion执行签名,会导致Signature对象缺少XMLSignature实例,触发报错。

修复代码:在设置签名后,先对Assertion进行编组,再执行签名:

Signature signature = createSignature();
assertion.setSignature(signature);
// 新增:先完成Assertion的XML编组
MarshallerFactory marshallerFactory = Configuration.getMarshallerFactory();
Marshaller marshaller = marshallerFactory.getMarshaller(assertion);
if (marshaller != null) {
    marshaller.marshall(assertion);
}
// 再执行签名
Signer.signObject(signature);

3. 私钥格式兼容性问题

当前代码使用PKCS8EncodedKeySpec解析私钥,但如果你的私钥是PKCS#1格式(PEM头为-----BEGIN RSA PRIVATE KEY-----),会解析失败导致Credential无效,进而引发签名异常。

两种修复方式:

  • 方式一:将PKCS#1格式私钥转换为PKCS#8格式,使用openssl命令:
    openssl pkcs8 -topk8 -inform PEM -in x509_priv.pem -outform PEM -nocrypt -out x509_priv_pkcs8.pem
    
  • 方式二:使用BouncyCastle库直接解析PKCS#1格式私钥,修改私钥解析代码:
    // 先添加BouncyCastle依赖
    Security.addProvider(new BouncyCastleProvider());
    // 替换原私钥解析部分
    PEMParser parser = new PEMParser(new StringReader(key));
    Object pemObject = parser.readObject();
    JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider("BC");
    PrivateKey privateKey = converter.getPrivateKey((PrivateKeyInfo) pemObject);
    

4. 初始化代码优化

DefaultBootstrap.bootstrap()是OpenSAML的全局初始化方法,应该只执行一次(比如放在类的静态代码块或项目启动类中),每次调用createSAMLResponse都执行初始化可能导致上下文冲突,引发异常。


内容的提问来源于stack exchange,提问作者Ali Azlan

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 15:33:15