0

我已使用 SOAPUI ( www.soapui.org )成功连接到远程 Web服务。但是,我无法从 CF9.2 成功调用它。

这是我的整个 CFC 函数。有动态变量,但我已经在soapUI界面中测试了输出并且它可以工作:

<cffunction name="getOrganisation" access="remote" returnType="any" output="true">

     <cfargument name="iPageNumber" type="any" required="false" default="0">
     <cfargument name="iPageSize" type="any" required="false" default="0">
     <cfargument name="bCurrentNamesOnly" type="boolean" required="false" default="1">
     <cfargument name="bExcludeNotRtos" type="boolean" required="false" default="0">
     <cfargument name="bExcludeRtoWithoutActiveRegistration" type="boolean" required="false" default="0">
     <cfargument name="sFilter" type="any" required="false" default="">
     <cfargument name="bIncludeCode" type="boolean" required="false" default="1">
     <cfargument name="sRegistrationManagers" type="any" required="false" default="">
     <cfargument name="sClassificationFilters" type="any" required="false" default="">
     <cfargument name="sScheme" type="any" required="false" default="">

     <cfset var endpoint = "https://ws.staging.training.gov.au/Deewr.Tga.WebServices/OrganisationService.svc/Organisation">

     <cfsavecontent variable="soapBody">
          <cfoutput>
               <soapenv:Envelope
                    xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" 
                    xmlns:ser="http://training.gov.au/services/" 
                    xmlns:arr="http://schemas.microsoft.com/2003/10/Serialization/Arrays"> 
                   <soapenv:Header/>               
                        <soapenv:Body>        
                        <ser:Search>
                            <ser:request>
                                <ser:PageNumber>#arguments.iPageNumber#</ser:PageNumber>
                                <ser:PageSize>#arguments.iPageSize#</ser:PageSize>
                                <ser:ClassificationFilters>
                                   <ser:ClassificationFilter>
                                      <ser:Scheme>#arguments.sScheme#</ser:Scheme>
                                      <ser:Values>
                                         <cfif len(arguments.sClassificationFilters)>
                                            <cfloop list="#arguments.sClassificationFilters#" index="item">
                                                <arr:string>#item#</arr:string>
                                            </cfloop>
                                        </cfif>
                                      </ser:Values>
                                   </ser:ClassificationFilter>
                                </ser:ClassificationFilters>
                                <ser:CurrentNamesOnly>#arguments.bCurrentNamesOnly#</ser:CurrentNamesOnly>
                                <ser:ExcludeNotRtos>#arguments.bExcludeNotRtos#</ser:ExcludeNotRtos>
                                <ser:ExcludeRtoWithoutActiveRegistration>#arguments.bExcludeRtoWithoutActiveRegistration#</ser:ExcludeRtoWithoutActiveRegistration>
                                <ser:Filter>#arguments.sFilter#</ser:Filter>
                                <ser:IncludeCode>#arguments.bIncludeCode#</ser:IncludeCode>
                                <ser:RegistrationManagers>
                                   <cfif len(arguments.sRegistrationManagers)>
                                        <cfloop list="#arguments.sRegistrationManagers#" index="item">
                                            <arr:string>#item#</arr:string>
                                        </cfloop>
                                    </cfif>
                                </ser:RegistrationManagers>
                             </ser:request>
                        </ser:Search>
                    </soapenv:Body>
                </soapenv:Envelope>
            </cfoutput>            
        </cfsavecontent>

        <cfhttp
                url="#endpoint#" 
                method="post"
                username="#variables.username#"
                password="#variables.password#">

                <cfhttpparam type="header" name="accept-encoding" value="no-compression" />
                <cfhttpparam type="xml" value="#trim(soapBody)#"/>

        </cfhttp>

       <cfdump var="#cfhttp.FileContent#"><cfabort>

      <cfreturn cfhttp.FileContent>
</cffunction>

运行这个,我得到错误:

An error occurred when verifying security for the message.

以下是完整的返回 xml

<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
    <s:Body>
        <s:Fault>
            <faultcode xmlns:a="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">a:InvalidSecurity</faultcode>
            <faultstring xml:lang="en-AU">An error occurred when verifying security for the message.</faultstring>
        </s:Fault>
    </s:Body>
</s:Envelope> 

因此,这似乎是一个授权问题。

这是 SoapUI 请求屏幕:

在此处输入图像描述

那么,如何构造 cfhttp 或者 cfinvoke 来模拟soapUI 调用?

编辑

SOAP 请求 XML

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ser="http://training.gov.au/services/" xmlns:arr="http://schemas.microsoft.com/2003/10/Serialization/Arrays">
   <soapenv:Header/>
   <soapenv:Body>
      <ser:Search>
         <ser:request>
            <ser:PageNumber>0</ser:PageNumber>
            <ser:PageSize>0</ser:PageSize>
            <ser:ClassificationFilters>
               <ser:ClassificationFilter>
                  <ser:Scheme></ser:Scheme>
                  <ser:Values>
                     <arr:string></arr:string>
                  </ser:Values>
               </ser:ClassificationFilter>
            </ser:ClassificationFilters>
            <ser:CurrentNamesOnly>true</ser:CurrentNamesOnly>
            <ser:ExcludeNotRtos>0</ser:ExcludeNotRtos>
            <ser:ExcludeRtoWithoutActiveRegistration>0</ser:ExcludeRtoWithoutActiveRegistration>
            <ser:Filter></ser:Filter>
            <ser:IncludeCode>1</ser:IncludeCode>
            <ser:RegistrationManagers>
               <arr:string></arr:string>
            </ser:RegistrationManagers>
         </ser:request>
      </ser:Search>
   </soapenv:Body>
</soapenv:Envelope>

编辑 2

更多信息:

4

3 回答 3

0

SOAPUI 是一个很棒的工具,我最近在调试 SOAP 请求或将它们从静态 .wsdl 文件传输到 ColdFusion 组件时经常使用它。

首先要查看的是错误消息本身:

<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
  <s:Body>
    <s:Fault>
      <faultcode xmlns:a="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">a:InvalidSecurity</faultcode>
      <faultstring xml:lang="en-AU">An error occurred when verifying security for the message.  </faultstring>
    </s:Fault>
  </s:Body>
</s:Envelope>

faultcode 节点包含指向 OASIS 安全命名空间文档的链接:

http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd

在浏览器中查看,您可以看到需要哪些安全值,并且可以阐明命名约定。因此,我们可以从中确认您确实需要使用以下格式发送以下内容:

  • 用户名
  • 密码

在无法访问您在 SOAPUI 中测试的请求的情况下,我们还可以看到 Username 和 Password 值在属性窗口中可见(您从上面的界面抓取屏幕)。

这些值是否在 SOAPUI 中请求的 XML 中设置?也许在 SOAP 请求标头中,如下所示:

<soapenv:Header>
  <authInfo xsi:type="soap:authentication">
    <Username xsi:type="xsd:string">?</Username>
    <Password xsi:type="xsd:string">?</Password>
  </authInfo>
</soapenv:Header>

如果是这种情况,您还需要在构建soapBody 变量时在您的ColdFusion 组件中包含此标头。

另外,您可以直接在浏览器中查看 SOAP 请求的 URL 以了解它期望的变量吗?

https://ws.staging.training.gov.au/Deewr.Tga.WebServices/OrganisationService.svc/Organisation

您的 CFC 和 soapBody 的整体结构看起来不错,因此无法访问您在 SOAPUI 中运行的文件以查看它的运行、调试它并提供答案会有点棘手。

如果您可以排除上述所有可能性但仍有问题,请告诉我。

于 2012-07-31T08:20:41.947 回答
0

我认为您需要在 cfhttpparam 标记中发布用户名和密码,而不是作为 cfhttp 标记中的属性。

于 2012-07-31T01:01:07.323 回答
0

是的,您最初并没有传递 Oasis 安全标头。然而,到目前为止,这里的答案可能不会奏效。Oasis 标准可以允许用户/密码/nonce/digest/created 和编码属性的多种不同组合。

您可能发现也可能没有发现,Axis 1 库(CF9 和更低的用户 Axis 1(版本 1.1-1.4);CF10 也有 Axis 2(Axis 版本 1.6))。因此,您必须通过 POST (cfhttp/http) 来传递和接收 Oasis 标头。Axis 1 库会因为试图理解标题区域中的 Oasis 节点而哭泣。**

由于您手动发布的,因此您可以简单地在您的标题中构建您的 Oasis 标题,就像 Matt Gifford 的示例一样。

根据接收方的确切期望,您可能会遇到以下情况:

<wsse:Security soap:mustUnderstand="true" xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" xmlns:soap="http://www.w3.org/2003/05/soap-envelope">
  <wsse:UsernameToken wsu:Id="UsernameToken-15">
    <wsse:Username>
      <!-- Removed-->
    </wsse:Username>
    <wsse:Password>
      <!-- Removed-->
    </wsse:Password>
    <wsse:Nonce>
      <!-- Removed-->
    </wsse:Nonce>
    <wsu:Created>2012-07-11T02:02:48.410Z</wsu:Created>
  </wsse:UsernameToken>
</wsse:Security>

或者这个(来自早期包层方法预输出):

<wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" soapenv:mustUnderstand="1">
  <wsse:UsernameToken xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" >
  <wsse:Username>#Arguments.szUserName#</wsse:Username>
  <wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0##PasswordText">#Arguments.szPassword#</wsse:Password>
  </wsse:UsernameToken>
</wsse:Security>

或者这个(最终输出):

<wsse:Security soapenv:mustUnderstand="1"xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
  <wsse:UsernameToken wsu:Id="UsernameToken-974900"xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
    <wsse:Username>SuperJellyMan</wsse:Username>
    <wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">jellybeanboom</wsse:Password>
    <wsse:Nonce EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary">SmVsbHlCZWFuQm9vbTk3NDkwMA==</wsse:Nonce>
    <wsu:Created>2012-07-26T17:00:34Z</wsu:Created>
  </wsse:UsernameToken>
</wsse:Security>

等等等等。我见过人们调用 java 库来更动态地执行此操作。我只是构建了一些简单的方法来构建我的 nonce 并格式化我的 xml 时间戳。但基本上,构建某种函数或其他辅助方法以使用适当的 Oasis 属性和 xsd 引用来覆盖 xml 中所需的值,然后将其插入手动发布请求的标题中是非常简单的。

棘手的部分是如果他们不告诉您,他们确切地知道 Oasis 安全标头中想要什么。xsd 仅显示根据 Oasis 标准可以接受的内容,但不一定显示他们正在使用什么(以及以何种组合方式)。如果你能从他们那里得到,你可以很容易地从 Oasis 文档中把它们放在一起。

通常,组合是用户/密码(第一个示例)。如果使用 Nonce,通常还会使用密码摘要(密码获取 ##PasswordDigest 而不是 ##PasswordText),并且是创建时间和密码的编码散列(有关更多说明,请参阅 Oasis 文档)。

但是,我从事的最后一个项目有一个纯文本密码加上一个没有摘要的随机数。不完全合乎逻辑,因为随机数应该使虚假请求更加困难并使用需要由接收者解码并与验证进行比较的非明文密码......所以这完全取决于他们想要什么的服务/使用/需要。

您应该能够将您编造的 Oasis 标头粘贴到 SoapUI 中,并很快从他们的响应中找出他们可能需要/想要的内容。

如果他们指定了用户/密码(作为文本),您可能会发现这些示例之一(可能是第二个)是可行的,并且没有传递 nonce 和 created 元素。

** 当 Oasis 与 CF9 或更少使用实际 SOAP 方法(Web 服务存根,而不是手动发布)一起使用时,这里是 Axis 1 故障的有趣参考:**

调用 Web 服务操作时返回的错误是:

AxisFault faultCode:{ http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd }InvalidSecurity faultSubcode:faultString:验证消息安全性时出错. 故障演员:故障节点:
faultDetail: { http://xml.apache.org/axis/ }stackTrace:验证消息的安全性时发生错误。在 org.apache.axis.message.SOAPFaultBuilder.createFault(SOAPFaultBuilder.java:221) 在 org.apache.axis.message.SOAPFaultBuilder.endElement(SOAPFaultBuilder.java:128) 在 org.apache.axis.encoding.DeserializationContext.endElement (反序列化上下文.java:1087)
在 org.apache.xerces.parsers.AbstractSAXParser.endElement(Unknown Source) at org.apache.xerces.impl.XMLNSDocumentScannerImpl.scanEndElement(Unknown Source) at org.apache.xerces.impl.XMLDocumentFragmentScannerImpl$FragmentContentDispatcher.dispatch(Unknown Source)在 org.apache.xerces.impl.XMLDocumentFragmentScannerImpl.scanDocument(Unknown Source) 在 org.apache.xerces.parsers.XML11Conf...

[编辑] 这里有更多信息,因为我正在开发另一个类似的网络服务。您的 [SOAP] xml 不可能正常工作。我也使用soapUI,除非我在适当的位置添加安全标头值,否则服务将准确返回您收到的响应(“InvalidSecurity”),因此不太确定该xml 是您应该尝试复制的内容。我还成功构建了一个简单的基于 cf 的脚本版本来构建正确生成标题并将其添加到 createObject() 调用的 javastubbed webservice 的标题:

doc = xmlNew();
doc['Security'] = XmlElemNew(doc,'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd', 'Security');
doc.Security['UsernameToken'] = XmlElemNew(doc,  'UsernameToken');
doc.Security.UsernameToken['Username'] = XmlElemNew(doc,  'Username');
doc.Security.UsernameToken.username.XmlText = "TESTERDUDE" ;
doc.Security.UsernameToken.username.XmlAttributes["xsi:type"] = "xsd:string";
doc.Security.UsernameToken['Password'] = XmlElemNew(doc, 'Password');
doc.Security.UsernameToken.password.XmlText = "YetAnotherPassword";
doc.Security.UsernameToken.password.XmlAttributes["xsi:type"] = "xsd:string";
doc.Security.UsernameToken.password.XmlAttributes["Type"] = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0##PasswordDigest";
doc.Security.UsernameToken['Nonce'] = XmlElemNew(doc, 'Nonce');
doc.Security.UsernameToken.nonce.XmlText = "tKUH8ab3Rokm4t6IAlgcdg9yaEw="; // This would be generated if needed
doc.Security.UsernameToken.nonce.XmlAttributes["xsi:type"] = "xsd:string";
doc.Security.UsernameToken['Created'] = XmlElemNew(doc, "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd", 'Created');
doc.Security.UsernameToken.created.XmlText = "2010-08-10T10:52:42Z";
doc.Security.UsernameToken.created.XmlAttributes["xsi:type"] = "xsd:string";
addSOAPRequestHeader(ws, "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd", "Security", doc);

这会正确生成 [使用获取整个 xml 请求的 getSoapRequest() 从服务器端 xml 获取]:

<soapenv:Header>
  <Security soapenv:actor="" soapenv:mustUnderstand="0" xmlns="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" xmlns:ns1="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
    <UsernameToken>
      <Username xsi:type="xsd:string">TESTERDUDE</Username>
      <Password xsi:type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest">My Password is my Voice</Password>
      <Nonce xsi:type="xsd:string">tKUH8ab3Rokm4t6IAlgcdg9yaEw=</Nonce>
     <Created xmlns="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" xmlns:ns2="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" xsi:type="xsd:string">2010-08-10T10:52:42Z</Created>
    </UsernameToken>
  </Security>
</soapenv:Header>
于 2012-08-23T23:02:22.757 回答