SAML 响应的 SAML2.0 签名验证失败

SAML2.0 signature validation failed for SAML Response

我已经创建了 SAML2.0 响应并使用 OpenSAML java 库对其进行了签名。尽管创建的 SAML 是有效的 XML,但签名无效(使用在线 SAML 工具验证)而且我的 SP 无法使用提供的证书验证签名。我可能在代码中使用 'Signature' 或证书做错了什么。我对 SAML/SSO/Digital 签名完全陌生,不知道从这里去哪里。请帮我解决这个问题。任何建议或指导将不胜感激。

下面是我的代码和签名的 SAMLresponse(我不是 Java 人并且是 SAML/SSO 的新手,所以参考了很多博客来开发这个代码)。

    public class SAMLWriter {
static Logger LOGGER = LoggerFactory.getLogger(SAMLWriter.class);
public static void main(String[] args) throws Throwable {
    try {
        SAMLInputContainer input = new SAMLInputContainer();
        input.strIssuer = "http://synesty.com";
        input.strNameID = "UserJohnSmith";
         input.sessionId = "abcdedf1234567";

        Map<String, String> customAttributes = new HashMap<String, String>();
        customAttributes.put("Value", "123456");

        input.attributes = customAttributes;

        Response response = SAMLWriter.buildDefaultResponse(input);

        Signature  signature = createSignature();
        response.setSignature(signature);

        ResponseMarshaller rMarshaller = new ResponseMarshaller();
       Element xmlString = rMarshaller.marshall(response);

       Signer.signObject(signature);

       String originalAssertionString = XMLHelper.prettyPrintXML(xmlString);

         System.out.println(originalAssertionString);

        //LOGGER.debug(originalAssertionString);

    } catch (MarshallingException e) {
        e.printStackTrace();
    }   
}

private static XMLObjectBuilderFactory builderFactory;

public static XMLObjectBuilderFactory getSAMLBuilder() throws ConfigurationException {

    if (builderFactory == null) {
        // OpenSAML 2.3
        DefaultBootstrap.bootstrap();
        builderFactory = Configuration.getBuilderFactory();
    }

    return builderFactory;
}

@SuppressWarnings("rawtypes")
public static Attribute buildStringAttribute(String name, String value, XMLObjectBuilderFactory builderFactory)
        throws ConfigurationException {
    SAMLObjectBuilder attrBuilder = (SAMLObjectBuilder) getSAMLBuilder().getBuilder(Attribute.DEFAULT_ELEMENT_NAME);
    Attribute attrFirstName = (Attribute) attrBuilder.buildObject();
    attrFirstName.setName(name);
    attrFirstName.setFriendlyName(name);
    attrFirstName.setNameFormat("urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified");

    // Set custom Attributes
    XMLObjectBuilder stringBuilder = getSAMLBuilder().getBuilder(XSString.TYPE_NAME);
    XSString attrValueFirstName = (XSString) stringBuilder.buildObject(AttributeValue.DEFAULT_ELEMENT_NAME,    XSString.TYPE_NAME);
    attrValueFirstName.setValue(value);

    attrFirstName.getAttributeValues().add(attrValueFirstName);
    return attrFirstName;
}

/**
 * Helper method which includes some basic SAML fields which are part of almost
 * every SAML Assertion.
 */
@SuppressWarnings("rawtypes")
public static Response buildDefaultResponse(SAMLInputContainer input) {
    try {
        // Create the NameIdentifier
        SAMLObjectBuilder nameIdBuilder = (SAMLObjectBuilder) SAMLWriter.getSAMLBuilder()
                .getBuilder(NameID.DEFAULT_ELEMENT_NAME);
        NameID nameId = (NameID) nameIdBuilder.buildObject();
        nameId.setValue(input.getStrNameID());
        nameId.setNameQualifier(input.getStrNameQualifier());
        // nameId.setFormat(NameID.UNSPECIFIED);
        nameId.setFormat("urn:oasis:names:tc:SAML:2.0:nameid-format:unspecified");

        // Create the SubjectConfirmation

        SAMLObjectBuilder confirmationMethodBuilder = (SAMLObjectBuilder) SAMLWriter.getSAMLBuilder()
                .getBuilder(SubjectConfirmationData.DEFAULT_ELEMENT_NAME);
        SubjectConfirmationData confirmationMethod = (SubjectConfirmationData) confirmationMethodBuilder
                .buildObject();
        DateTime now = new DateTime();
        confirmationMethod.setNotBefore(now);
        confirmationMethod.setNotOnOrAfter(now.plusMinutes(2));
        confirmationMethod.setRecipient("MYCLIENTWEBSITE");

        SAMLObjectBuilder subjectConfirmationBuilder = (SAMLObjectBuilder) SAMLWriter.getSAMLBuilder()
                .getBuilder(SubjectConfirmation.DEFAULT_ELEMENT_NAME);
        SubjectConfirmation subjectConfirmation = (SubjectConfirmation) subjectConfirmationBuilder.buildObject();

        subjectConfirmation.setMethod("urn:oasis:names:tc:SAML:2.0:cm:bearer");
        subjectConfirmation.setSubjectConfirmationData(confirmationMethod);

        // Create the Subject
        SAMLObjectBuilder subjectBuilder = (SAMLObjectBuilder) SAMLWriter.getSAMLBuilder()
                .getBuilder(Subject.DEFAULT_ELEMENT_NAME);
        Subject subject = (Subject) subjectBuilder.buildObject();

        subject.setNameID(nameId);
        subject.getSubjectConfirmations().add(subjectConfirmation);

        // Create Authentication Statement
        SAMLObjectBuilder authStatementBuilder = (SAMLObjectBuilder) SAMLWriter.getSAMLBuilder()
                .getBuilder(AuthnStatement.DEFAULT_ELEMENT_NAME);
        AuthnStatement authnStatement = (AuthnStatement) authStatementBuilder.buildObject();
        // authnStatement.setSubject(subject);
        // authnStatement.setAuthenticationMethod(strAuthMethod);
        DateTime now2 = new DateTime();
        authnStatement.setAuthnInstant(now2);
        authnStatement.setSessionIndex(input.getSessionId());
        authnStatement.setSessionNotOnOrAfter(now2.plus(input.getMaxSessionTimeoutInMinutes()));

        SAMLObjectBuilder authContextBuilder = (SAMLObjectBuilder) SAMLWriter.getSAMLBuilder()
                .getBuilder(AuthnContext.DEFAULT_ELEMENT_NAME);
        AuthnContext authnContext = (AuthnContext) authContextBuilder.buildObject();

        SAMLObjectBuilder authContextClassRefBuilder = (SAMLObjectBuilder) SAMLWriter.getSAMLBuilder()
                .getBuilder(AuthnContextClassRef.DEFAULT_ELEMENT_NAME);
        AuthnContextClassRef authnContextClassRef = (AuthnContextClassRef) authContextClassRefBuilder.buildObject();

        authnContextClassRef
                .setAuthnContextClassRef("urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport");

        authnContext.setAuthnContextClassRef(authnContextClassRef);
        authnStatement.setAuthnContext(authnContext);

        // Builder Attributes
        SAMLObjectBuilder attrStatementBuilder = (SAMLObjectBuilder) SAMLWriter.getSAMLBuilder()
                .getBuilder(AttributeStatement.DEFAULT_ELEMENT_NAME);
        AttributeStatement attrStatement = (AttributeStatement) attrStatementBuilder.buildObject();


       // Create the attribute statement
        Map attributes = input.getAttributes();
        if (attributes != null) {
            Iterator keySet = attributes.keySet().iterator();
            while (keySet.hasNext()) {
                String key1 = keySet.next().toString();
                String val = attributes.get(key1).toString();
                Attribute attrFirstName = buildStringAttribute(key1, val, getSAMLBuilder());
                attrStatement.getAttributes().add(attrFirstName);
            }
        } 


        SAMLObjectBuilder doNotCacheConditionBuilder = (SAMLObjectBuilder) SAMLWriter.getSAMLBuilder()
                .getBuilder(OneTimeUse.DEFAULT_ELEMENT_NAME);
        Condition condition = (Condition) doNotCacheConditionBuilder.buildObject();


        SAMLObjectBuilder audienceBuilder = (SAMLObjectBuilder) SAMLWriter.getSAMLBuilder()
                .getBuilder(Audience.DEFAULT_ELEMENT_NAME);
        Audience audience = (Audience) audienceBuilder.buildObject();
        audience.setAudienceURI("https://my.audience.com");

        SAMLObjectBuilder audienceRestrictionBuilder = (SAMLObjectBuilder) SAMLWriter.getSAMLBuilder()
                .getBuilder(AudienceRestriction.DEFAULT_ELEMENT_NAME);
        AudienceRestriction audienceRestriction = (AudienceRestriction) audienceRestrictionBuilder.buildObject();
        audienceRestriction.getAudiences().add(audience);

        SAMLObjectBuilder conditionsBuilder = (SAMLObjectBuilder) SAMLWriter.getSAMLBuilder()
                .getBuilder(Conditions.DEFAULT_ELEMENT_NAME);
        Conditions conditions = (Conditions) conditionsBuilder.buildObject();
        conditions.getConditions().add(condition);
        conditions.getAudienceRestrictions().add(audienceRestriction);

        // Create Issuer
        SAMLObjectBuilder issuerBuilder = (SAMLObjectBuilder) SAMLWriter.getSAMLBuilder()
                .getBuilder(Issuer.DEFAULT_ELEMENT_NAME);
        Issuer issuer = (Issuer) issuerBuilder.buildObject();
        issuer.setValue(input.getStrIssuer());
        issuer.setFormat("urn:oasis:names:tc:SAML:2.0:nameid-format:entity");

        // Create statusCode
        SAMLObjectBuilder codeBuilder = (SAMLObjectBuilder) SAMLWriter.getSAMLBuilder()
                .getBuilder(StatusCode.DEFAULT_ELEMENT_NAME);
        StatusCode statusCode = (StatusCode) codeBuilder.buildObject();
        statusCode.setValue(StatusCode.SUCCESS_URI);

        // Create status
        SAMLObjectBuilder statusBuilder = (SAMLObjectBuilder) SAMLWriter.getSAMLBuilder()
                .getBuilder(Status.DEFAULT_ELEMENT_NAME);
        Status status = (Status) statusBuilder.buildObject();
        status.setStatusCode(statusCode);

        // Create the assertion
        SAMLObjectBuilder assertionBuilder = (SAMLObjectBuilder) SAMLWriter.getSAMLBuilder()
                .getBuilder(Assertion.DEFAULT_ELEMENT_NAME);
        Assertion assertion = (Assertion) assertionBuilder.buildObject();
        assertion.setIssuer(issuer);

        assertion.setID(input.getSessionId());
        assertion.setSubject(subject);
        assertion.setIssueInstant(now);
        assertion.setVersion(SAMLVersion.VERSION_20);
        assertion.getAuthnStatements().add(authnStatement);
        assertion.getAttributeStatements().add(attrStatement);
        assertion.setConditions(conditions);

        SAMLObjectBuilder responseBuilder = (SAMLObjectBuilder) SAMLWriter.getSAMLBuilder()
                .getBuilder(Response.DEFAULT_ELEMENT_NAME);
        Response response = (Response) responseBuilder.buildObject();

        SAMLObjectBuilder issuBuilder = (SAMLObjectBuilder) SAMLWriter.getSAMLBuilder()
                .getBuilder(Issuer.DEFAULT_ELEMENT_NAME);
        Issuer issuer1 = (Issuer) issuBuilder.buildObject();
        issuer1.setValue(input.getStrIssuer());
        issuer1.setFormat("urn:oasis:names:tc:SAML:2.0:nameid-format:entity");

        // response.setIssuer(issuer);
        response.setDestination("http://MYCLIENTWEBSITE.com");
        response.setID("abcd123456");
        response.setIssueInstant(now);
        response.setIssuer(issuer1);
        response.setStatus(status);
        response.getAssertions().add(assertion);

        return response;
    } catch (Exception e) {
        e.printStackTrace();
    }
    return null;
}

 static class SAMLInputContainer {
    private String strIssuer;
    private String strNameID;
    private String strNameQualifier;
    private String sessionId;
    private int maxSessionTimeoutInMinutes = 15; // default is 15 minutes

    private Map<String, String> attributes;

    public String getStrIssuer() {
        return strIssuer;
    }

    public void setStrIssuer(String strIssuer) {
        this.strIssuer = strIssuer;
    }

    public String getStrNameID() {
        return strNameID;
    }

    public void setStrNameID(String strNameID) {
        this.strNameID = strNameID;
    }

    public String getStrNameQualifier() {
        return strNameQualifier;
    }

    public void setStrNameQualifier(String strNameQualifier) {
        this.strNameQualifier = strNameQualifier;
    }

    public void setAttributes(Map<String, String> attributes) {
        this.attributes = attributes;
    }

    public Map<String, String> getAttributes() {
        return attributes;
    }

    public void setSessionId(String sessionId) {
        this.sessionId = sessionId;
    }

    public String getSessionId() {
        return sessionId;
    }

    public void setMaxSessionTimeoutInMinutes(int maxSessionTimeoutInMinutes) {
        this.maxSessionTimeoutInMinutes = maxSessionTimeoutInMinutes;
    }

    public int getMaxSessionTimeoutInMinutes() {
        return maxSessionTimeoutInMinutes;
    }
}

 private static Signature createSignature() throws Throwable {
     KeyStore ks = KeyStore.getInstance("JKS");
     char[] password = "password".toCharArray();
     FileInputStream fis = new FileInputStream("my-custom.jks");

     ks.load(fis, password);
     fis.close();

     String alias = "my-custom-alias";

         PrivateKey key = (PrivateKey) ks.getKey(alias, password);

     SignatureBuilder signatureBuilder = (SignatureBuilder) SAMLWriter.getSAMLBuilder()
             .getBuilder(Signature.DEFAULT_ELEMENT_NAME);
     Signature signature = (Signature) signatureBuilder.buildObject();

     KeyStore.ProtectionParameter protParam = new KeyStore.PasswordProtection(password);

     KeyStore.PrivateKeyEntry pkEntry = (KeyStore.PrivateKeyEntry) ks.getEntry(alias, protParam);

     X509Certificate certificate = (X509Certificate) pkEntry.getCertificate();
     BasicX509Credential credential = new BasicX509Credential();
     credential.setEntityCertificate(certificate);
     credential.setPrivateKey(key);

     signature.setSigningCredential(credential);
     signature.setSignatureAlgorithm(SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA256);
     signature.setCanonicalizationAlgorithm(SignatureConstants.ALGO_ID_C14N_EXCL_OMIT_COMMENTS);

    SecurityConfiguration secConfig = Configuration.getGlobalSecurityConfiguration();

    SecurityHelper.prepareSignatureParams(signature, credential, secConfig, null);
    return signature;
 }
}

SAML 响应

<?xml version="1.0" encoding="UTF-8"?>
<saml2p:Response Destination="http://MYCLIENTWEBSITE.com" ID="abcd123456"
    IssueInstant="2018-08-12T17:55:28.256Z" Version="2.0"
    xmlns:saml2p="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:xs="http://www.w3.org/2001/XMLSchema">
    <saml2:Issuer
        Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity" xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion">http://synesty.com</saml2:Issuer>
    <ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
        <ds:SignedInfo>
            <ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
            <ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
            <ds:Reference URI="#abcd123456">
                <ds:Transforms>
                    <ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
                    <ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#">
                        <ec:InclusiveNamespaces PrefixList="xs" xmlns:ec="http://www.w3.org/2001/10/xml-exc-c14n#"/>
                    </ds:Transform>
                </ds:Transforms>
                <ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
                <ds:DigestValue>5xbz6J9daVi7sM9pLTuJ0+x0GIo=</ds:DigestValue>
            </ds:Reference>
        </ds:SignedInfo>
        <ds:SignatureValue>f+Ynig8GU6/pqDbnRYxzSCZYTrCUflmXjvgcjfgP5Wxr2qju9gHZszGQny5xgaj+MTkSwF1Y7wREO5f3srVs+Xt7fITUhxyyeop0py/zbbCgT48Vx82eCy5ISo5wGGj6WHVvq6j/UV7UYurAByqZOHKfkKcu551fLkGXaTixAVSeM9tgZ3DAxVn2kkdEvcw8ZIE2GCfOmQApjH9J5I+OB2JuQwGxyEJjYDLVjWd5OrJ/k619BfVwiQ0NMjNGXY3kXCnFNbHOh5k9iYmbmZWl4Kvuf1lZiHpJ/LAcusbk1oyscMZO1d3iKbEDTT8pcV1QnZQ7WLEBzrCoq8mS/DcYmA==</ds:SignatureValue>
        <ds:KeyInfo>
            <ds:X509Data>
                <ds:X509Certificate>MIIDUzCCAjugAwIBAgIEM1ls8DANBgkqhkiG9w0BAQsFADBaMQswCQYDVQQGEwJVUzELMAkGA1UE
CBMCUEExDjAMBgNVBAcTBUV4dG9uMQ0wCwYDVQQKEwRzZWxmMQ0wCwYDVQQLEwRzZWxmMRAwDgYD
VQQDEwdoIG1hcmFtMB4XDTE4MDcxMTAzMjAyNloXDTI4MDcwODAzMjAyNlowWjELMAkGA1UEBhMC
VVMxCzAJBgNVBAgTAlBBMQ4wDAYDVQQHEwVFeHRvbjENMAsGA1UEChMEc2VsZjENMAsGA1UECxME
c2VsZjEQMA4GA1UEAxMHaCBtYXJhbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAIts
MsABm9v2JgYq9H5fBFlR2Y04VbGIM9dEgq7kXfteHzNB5zPyIBEh/CpIuxKfkg0cftMhglL5aFGR
PgbfMMZ3w+zZDWNg5SY2O8WkcBBcC55GNX5bgZ0uYYuefiqqKOIh8QFZYI0sNW8eV+rJduyNNOgR
4ZO9yS950FBhmrmjE/b4blNMAnUH8q0MRjDzF/3vBcrQueLLCfCgxK/5Gv48XsiQNCYhGMEP1RM+
aX466hITH9IBri7bKNyh5REMYZCCHH3N6H80gmVxJRce5DVXHg9hqr+eSsnM0izSyy3GmkNvrAQZ
U7ZAc5pfOzUHhtN6jlkfSEDUDY5UUMn4SOsCAwEAAaMhMB8wHQYDVR0OBBYEFBbMWQqbqcpkvHh2
tpO5fMX1QH3IMA0GCSqGSIb3DQEBCwUAA4IBAQBGxJnq8Py5BA0UXijgaxdZn9ggaIz+IvknOpYg
ExCj5bG12sHLjAKXjDWpuB/9gX2SbrE3LsyHD2MBUqnVTfO2ZsWgiBGTfd2Wl6Yy1AuoMIigHu2A
xtu5G6/DeD/KEKYkQj9GcUg/rNAMBfKKSRb8Pif77AOQPoJgbSb8gxsKy8K3b7KjJtPAOgkQ0hDW
y9eRrbaV8Zcj77MPs7UppvjpI0n7FhyzJp1zpZJitYm978CrVs/qARos9VPAs7WnrOH2LR92+OaY
pSN/d0pXYRRI0fGzw8grpgy9kcV9SD6IApc3K5qoq1DskRZVvO1QVYO+QNAkM9Xca6vTDWOJ9qC5</ds:X509Certificate>
            </ds:X509Data>
        </ds:KeyInfo>
    </ds:Signature>
    <saml2p:Status>
        <saml2p:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/>
    </saml2p:Status>
    <saml2:Assertion ID="abcdedf1234567"
        IssueInstant="2018-08-12T17:55:28.256Z" Version="2.0" xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion">
        <saml2:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity">http://synesty.com</saml2:Issuer>
        <saml2:Subject>
            <saml2:NameID Format="urn:oasis:names:tc:SAML:2.0:nameid-format:unspecified">UserJohnSmith</saml2:NameID>
            <saml2:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
                <saml2:SubjectConfirmationData
                    NotBefore="2018-08-12T17:55:28.256Z"
                    NotOnOrAfter="2018-08-12T17:57:28.256Z" Recipient="MYCLIENTWEBSITE"/>
            </saml2:SubjectConfirmation>
        </saml2:Subject>
        <saml2:Conditions>
            <saml2:OneTimeUse/>
            <saml2:AudienceRestriction>
                <saml2:Audience>https://my.audience.com</saml2:Audience>
            </saml2:AudienceRestriction>
        </saml2:Conditions>
        <saml2:AuthnStatement AuthnInstant="2018-08-12T17:55:28.659Z"
            SessionIndex="abcdedf1234567" SessionNotOnOrAfter="2018-08-12T17:55:28.674Z">
            <saml2:AuthnContext>
                <saml2:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport</saml2:AuthnContextClassRef>
            </saml2:AuthnContext>
        </saml2:AuthnStatement>
        <saml2:AttributeStatement>
            <saml2:Attribute FriendlyName="Value" Name="Value" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified">
                <saml2:AttributeValue
                    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">123456</saml2:AttributeValue>
            </saml2:Attribute>
        </saml2:AttributeStatement>
    </saml2:Assertion>
</saml2p:Response>

我相信 SAML 签名 is sensitive to whitespace。因此,签名后不应对其进行漂亮印刷。

替换

String originalAssertionString = XMLHelper.prettyPrintXML(xmlString);

String originalAssertionString = XMLHelper.nodeToString(xmlString);