ColdFusion CFHTTP连接到远程Web服务

时间:2012-07-30 23:44:44

标签: soap coldfusion

我已使用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请求屏幕:

enter image description here

那么,我如何构造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

更多信息:

3 个答案:

答案 0 :(得分:0)

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

答案 1 :(得分: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中测试的请求,我们还可以看到用户名和密码值在属性窗口中可见(您从上面的界面抓取屏幕)。

这些值是否在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中运行的文件以查看它运行,调试它并提供答案会有点棘手。

如果您可以排除上述所有可能性但仍然存在问题,请与我们联系。

答案 2 :(得分:0)

是的,您最初没有通过Oasis安全标头。到目前为止,这里的答案可能不会起作用。 Oasis标准可以允许许多用户/密码/现时/摘要/创建和编码属性的各种组合。

正如您可能找到或未找到的那样,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库来更动态地执行此操作。我只是构建了一些简单的方法来构建我的随机数并格式化我的xml时间戳。但基本上,构建某种函数或其他辅助方法来将xml中所需的值包含在适当的Oasis属性和xsd引用中,然后将其插入到手动帖子请求的标题中是非常简单的。

如果他们没有告诉你,那么棘手的部分就是在Oasis安全标题中确切地知道他们想要什么。 xsd简单地显示了根据Oasis标准可接受的内容,但不一定显示它们正在使用的内容(以及它们的组合)。如果你可以从他们那里得到它,你可以很容易地从Oasis docs中把它们放在一起。

通常,组合是User / Pwd(第一个例子)。如果使用Nonce,通常也会使用Password Digest(密码获取## PasswordDigest而不是## PasswordText),并且是创建的时间和密码的编码哈希(有关更多说明,请参阅Oasis文档)。

但是,我工作的最后一个项目有一个纯文本pwd加上一个没有摘要的nonce。不完全合乎逻辑,因为nonce应该使伪造的请求更加困难,并使用非明文密码,需要由接收器解码并进行比较以验证......所以完全取决于他们想要的服务/使用/需求。

你应该能够将你编制的Oasis标头粘贴到SoapUI中,并很快从他们的响应中快速找出他们可能需要/想要的东西。

如果他们指定了用户/密码(作为文本),您可能会发现其中一个示例(可能是第二个)可行并且不传递现时和创建的元素。

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

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

AxisFault faultCode: {http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd}InvalidSecurity faultSubcode: faultString: An error occurred when verifying security for the message. faultActor: faultNode: 
faultDetail: {http://xml.apache.org/axis/}stackTrace:An error occurred when verifying security for the message. at org.apache.axis.message.SOAPFaultBuilder.createFault(SOAPFaultBuilder.java:221) at org.apache.axis.message.SOAPFaultBuilder.endElement(SOAPFaultBuilder.java:128) at org.apache.axis.encoding.DeserializationContext.endElement(DeserializationContext.java:1087) 
at 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) at org.apache.xerces.impl.XMLDocumentFragmentScannerImpl.scanDocument(Unknown Source) at org.apache.xerces.parsers.XML11Conf... 

[编辑] 这是一个更多信息,因为我正在处理类似的另一个Web服务。 你的[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);

这正确地生成[使用getSoapRequest()从服务器端获取xml,它抓取整个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>