我创建了SAML2.0响应并使用OpenSAML Java库对其进行了签名。尽管创建的SAML是有效的XML,但签名无效(已使用在线SAML工具进行了验证),而且我的SP也无法使用提供的证书来验证签名。我的代码中的“签名”或证书可能做错了。我对SAML / SSO /数字签名是完全陌生的,不知道从何而来。请帮助我解决这个问题。任何建议或指导将不胜感激。
下面是我的代码和签名的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>
答案 0 :(得分:0)
我相信SAML签名is sensitive to whitespace。因此,在签名后不应将其漂亮打印。
替换
String originalAssertionString = XMLHelper.prettyPrintXML(xmlString);
使用
String originalAssertionString = XMLHelper.nodeToString(xmlString);