英文:
How to use Java's XPath library in conjunction with XML namespaces?
问题
以下是要翻译的内容:
我们希望记录用于对来自IdP的XML格式SAML响应进行签名的算法。示例响应如下:
<?xml version="1.0" encoding="UTF-8"?>
<saml2p:Response Destination="https://local.internal.company.de:443/saml/SSO" ID="_f4b74c8bd287c774ff132ad648b74c33"
InResponseTo="a15je30i854ji72e54egg30bd3jg622" IssueInstant="2020-08-10T08:54:48.272Z" Version="2.0"
xmlns:saml2p="urn:oasis:names:tc:SAML:2.0:protocol">
...
</saml2p:Response>
为了实现这一点,我们使用Java的内置XPath功能:
private XPathExpression setupXPathExpression(String xPath) throws XPathExpressionException {
XPath xPathTmp = XPathFactory.newInstance().newXPath();
xPathTmp.setNamespaceContext(new NamespaceContext() {
...
});
return xPathTmp.compile(xPath);
}
protected String extractResponseSignatureAlgorithm(String samlResponse) throws IOException, SAXException, XPathExpressionException, ParserConfigurationException {
String xPath = "//ds:SignatureMethod";
...
NodeList matches = (NodeList) xPathExpression.evaluate(xmlDocument, XPathConstants.NODESET);
...
String algo = signatureMethodNode.getAttributes().getNamedItem("Algorithm").getNodeValue();
...
int len = matches.getLength();
...
return matches.toString();
}
我在freeformatter.com上开发并验证了简单的XPath //ds:SignatureMethod
。不幸的是,它在Java中没有匹配到任何结果。在网上的一些研究建议配置自定义命名空间上下文,您可以在setupXPathExpression
中看到。
以下是用于执行和调试代码的单元测试:
@Test
public void testExtractResponseSignatureAlgorithm() throws IOException, SAXException, XPathExpressionException, ParserConfigurationException {
String samlResponse = IOUtils.toString(this.getClass().getResourceAsStream("/saml/sha256-response.xml"));
String actual = filter.extractResponseSignatureAlgorithm(samlResponse);
String expected = "http://www.w3.org/2001/04/xmlenc#sha256";
Assert.assertEquals(expected, actual);
}
我不知道如何进一步调试匹配过程,或者为什么这个简单的XPath没有匹配到明显存在于XML数据中的节点。
英文:
We want to log the algorithm used to sign XML-formatted SAML responses sent from the IdP. An exemplary response looks like this:
<?xml version="1.0" encoding="UTF-8"?>
<saml2p:Response Destination="https://local.internal.company.de:443/saml/SSO" ID="_f4b74c8bd287c774ff132ad648b74c33"
InResponseTo="a15je30i854ji72e54egg30bd3jg622" IssueInstant="2020-08-10T08:54:48.272Z" Version="2.0"
xmlns:saml2p="urn:oasis:names:tc:SAML:2.0:protocol">
<saml2:Issuer xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion">
https://local.internal.company.de:4443/idp/shibboleth
</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="#_f4b74c8bd287c774ff132ad648b74c33">
<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#"/>
</ds:Transforms>
<ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
<ds:DigestValue>+0000000000000000000000000000000000000+0000=</ds:DigestValue>
</ds:Reference>
</ds:SignedInfo>
<ds:SignatureValue>
00000+00000000000000000000000000000000000000000000000000000000+000000000/000
000000000000000000000000000000000000000000000/000000000000000000000000000000
000000000000000000000000000000+0000000000/0000000000+00000000000000000000000
000000000000000000000000000000000000/000000000000000000000000000000000+00000
00000000000000000000000000000000000000==
</ds:SignatureValue>
<ds:KeyInfo>
<ds:X509Data>
<ds:X509Certificate>
000000000000000000000000000/000000+00000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000000000000000
00000000000000000000/00000000000000000000000000000000000000/0000000000000000
000/00000000000000000+000000000000000000000000/0000000000000/000000000000000
0000000000000000000000000000/000000000000000+00000+0000000000000000000000000
000000000000000000000000000000000000000000000000000000000000000000000000/000
0000000000/00000000000000000000000000000000000000000000000000000/000+0000000
0000000000000000000000000000000000000000000000000000000000000000000000000000
000000000000000000000000000000000000000000000000000000000000000/00000000000/
00000+0000000000000000000/000000000000000000000000000+00000000000000000+0000
0000/0000000/00000000000000000/0000000000000000000000000000000000000000/00+0
000000000/0/00000000000000000000000000000000+0000000000000000000000000000000
0000000000+000000000000000000000000000000000000000000000000000==
</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:EncryptedAssertion xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion">
<xenc:EncryptedData Id="_00000000000000000000000000000000" Type="http://www.w3.org/2001/04/xmlenc#Element"
xmlns:xenc="http://www.w3.org/2001/04/xmlenc#">
<xenc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#aes128-cbc"
xmlns:xenc="http://www.w3.org/2001/04/xmlenc#"/>
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<xenc:EncryptedKey Id="_0000000000000000000000000000000" Recipient="de:company:platform"
xmlns:xenc="http://www.w3.org/2001/04/xmlenc#">
<xenc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p"
xmlns:xenc="http://www.w3.org/2001/04/xmlenc#">
<ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"
xmlns:ds="http://www.w3.org/2000/09/xmldsig#"/>
</xenc:EncryptionMethod>
<ds:KeyInfo>
<ds:X509Data>
<ds:X509Certificate>
0000000000000000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000000/0000000000000000000000
000000000000000000000000000000000/+00000000000000000000000000000000000000000
000000000000000000000000000000000000000000+00000000/000000/00000000000000000
000000000000000000000000000000000000/00000000000000000000/00000/000000000000
0000000000000000000000000000000000/00000000000000000000000000000000000000+00
00000000000000000000000000000/000000000000000000++00000000000000000000+00000
0000000000000000000000000000000000000000000000000000000000000000000000000000
000000+000000000000000000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000/0+00000000000000000000000
000000000000000000000+000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000000/00000000000/0000000000
00000000000000000000000000000000000000000000000+0000000+00000000000000000000
000000000000+0000000/0000+0000+000000000000000000000000000/00000000000000000
0000000000000000000000000000000000/0000000000000+000000000000000000000000000
000/00000000000000000+00000000+0000000000+0000000000000000000000000000000+00
0000000000000000000000000000000000000000000000000000000+00000000000000000000
0000000000000000000000000000000000/00000000000000000000000000000+00000000000
00000000000000000+000000000000000000000000000000000//00000000/00000000000000
0000/00/0000000/000000000000+00000000000000000000000000000000000000000000000
000+00000000000000000000000000000000000000000000
</ds:X509Certificate>
</ds:X509Data>
</ds:KeyInfo>
<xenc:CipherData xmlns:xenc="http://www.w3.org/2001/04/xmlenc#">
<xenc:CipherValue>00000000000000000000000000000000000000000000000/000+00/000000000000000000000
0000000000000000000000000000+00000000000000000000000000000000000000000000000
000000000000/000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000+0000000000000000000000000000/
0000000000000000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000000000000000
0000+000000000000000000+00000+000000000000000000000000000000000000000000000=
</xenc:CipherValue>
</xenc:CipherData>
</xenc:EncryptedKey>
</ds:KeyInfo>
<xenc:CipherData xmlns:xenc="http://www.w3.org/2001/04/xmlenc#">
<xenc:CipherValue>0000000000000000000000/00000000000000000000000000000000000000000000000000000
0000000000/000000000000000000000000000000+0000000000000000000000000000000000
0000000000000000000000000000000000/00000000000000000000000+00000000000000000
00000000000000/000000000000000000000000000/+00000000000000000000000000000000
00000000000000000000000000000000000000000000000000000000/0000000000000000000
0000000000000000000000000000000000000000000000000000000000000000000000000000
0000000/00000000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000+00000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000000000+00000
00000000000000000+000000000000000000000000/000000000000000000000000000000000
000000000000000000==
</xenc:CipherValue>
</xenc:CipherData>
</xenc:EncryptedData>
</saml2:EncryptedAssertion>
</saml2p:Response>
To achieve this we use Java's built-in XPath functionality:
private XPathExpression setupXPathExpression(String xPath) throws XPathExpressionException {
XPath xPathTmp = XPathFactory.newInstance().newXPath();
xPathTmp.setNamespaceContext(new NamespaceContext() {
@Override
public String getNamespaceURI(String prefix) {
// as per https://coderanch.com/t/649195/java/XPath-escape
if (prefix == null) {
throw new NullPointerException("Null prefix");
} else if ("saml2p".equals(prefix)) {
return "urn:oasis:names:tc:SAML:2.0:protocol";
} else if ("saml2".equals(prefix)) {
return "urn:oasis:names:tc:SAML:2.0:assertion";
} else if ("ds".equals(prefix)) {
return "http://www.w3.org/2000/09/xmldsig#";
} else if ("xenc".equals(prefix)) {
return "http://www.w3.org/2001/04/xmlenc#";
}
return XMLConstants.NULL_NS_URI;
}
@Override
public String getPrefix(String namespaceURI) {
throw new UnsupportedOperationException();
}
@Override
public Iterator<String> getPrefixes(String namespaceURI) {
throw new UnsupportedOperationException();
}
});
return xPathTmp.compile(xPath);
}
protected String extractResponseSignatureAlgorithm(String samlResponse) throws IOException, SAXException, XPathExpressionException, ParserConfigurationException {
// String xPath= "//*";
String xPath = "//ds:SignatureMethod";
DocumentBuilder documentBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
XPathExpression xPathExpression = setupXPathExpression(xPath);
InputSource is = new InputSource();
is.setCharacterStream(new StringReader(samlResponse));
Document xmlDocument = documentBuilder.parse(is);
NodeList matches = (NodeList) xPathExpression.evaluate(xmlDocument, XPathConstants.NODESET);
// manually traverse example XML to verify via debugger that the content is actually there
Node signatureMethodNode = xmlDocument.getFirstChild().getFirstChild().getNextSibling().getNextSibling().getNextSibling().getFirstChild().getNextSibling().getFirstChild().getNextSibling().getNextSibling().getNextSibling();
// is in fact "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256" when debugging unit test
String algo = signatureMethodNode.getAttributes().getNamedItem("Algorithm").getNodeValue();
// is 0 for xPath = "//ds:SignatureMethod";
// is 32 for xPath= "//*";
int len = matches.getLength();
return matches.toString();
}
I developed and verified the simple XPath //ds:SignatureMethod
using freeformatter.com. Unfortunately it doesn't match any results in Java. Some research on the web suggested to configure a custom namespace context which you can see in setupXPathExpression
.
Here is my unit test to execute and debug my code:
@Test
public void testExtractResponseSignatureAlgorithm() throws IOException, SAXException, XPathExpressionException, ParserConfigurationException {
String samlResponse = IOUtils.toString(this.getClass().getResourceAsStream("/saml/sha256-response.xml"));
String actual = filter.extractResponseSignatureAlgorithm(samlResponse);
String expected = "http://www.w3.org/2001/04/xmlenc#sha256";
Assert.assertEquals(expected, actual);
}
I don't know how to further debug the matching process or why this simple XPath doesn't match the node that clearly exists in the XML data.
答案1
得分: 2
在调用 setNamespaceContext()
似乎足以表示意图使用命名空间,实际上还需要调用 DocumentBuilderFactory.setNamespaceAware(true)。
另请参阅 https://stackoverflow.com/q/40796231/290085
英文:
While calling setNamespaceContext()
might seem to suffice to indicate intent to use namespaces, it actually also takes a call to DocumentBuilderFactory.setNamespaceAware(true).
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论