代码之家  ›  专栏  ›  技术社区  ›  chris01

Java:带有命名空间的XPath未获得预期结果

  •  0
  • chris01  · 技术社区  · 2 年前

    我们想要记录用于对从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">
        <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>
    

    为了实现这一点,我们使用Java内置的XPath功能:

    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();
    }
    

    我开发并验证了简单的XPath //ds:SignatureMethod 使用 freeformatter.com 。不幸的是,它与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数据中明显存在的节点不匹配。

    0 回复  |  直到 4 年前
        1
  •  2
  •   kjhughes    4 年前

    呼叫时 setNamespaceContext() 似乎足以表明使用名称空间的意图,但实际上还需要调用 DocumentBuilderFactory.setNamespaceAware(true)

    另请参阅 How does XPath deal with XML namespaces?