如何与XML命名空间一起使用Java的XPath库?

huangapple go评论59阅读模式
英文:

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).

See also https://stackoverflow.com/q/40796231/290085

huangapple
  • 本文由 发表于 2020年8月10日 20:54:16
  • 转载请务必保留本文链接:https://go.coder-hub.com/63340639.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定