代码之家  ›  专栏  ›  技术社区  ›  Brian Surowiec

如何在.NET中使用SOAP和不使用WSE签署Amazon Web服务请求

  •  3
  • Brian Surowiec  · 技术社区  · 15 年前

    亚马逊产品广告API(以前称为Amazon Associates Web Service或Amazon AWS)实施了一项新规则,即到2009年8月15日,所有向其发送的Web服务请求都必须签署。他们在自己的站点上提供了示例代码,展示了如何使用REST和SOAP在C中实现这一点。我使用的实现是SOAP。您可以找到示例代码 here ,我不包括它,因为有一个公平的数额。

    我遇到的问题是他们的示例代码使用wse 3,而我们当前的代码不使用wse。有人知道如何使用WSDL中自动生成的代码来实现这个更新吗?如果我不需要切换到WSE 3,我希望现在不必切换到WSE 3,因为此更新更像是一个快速补丁,可以让我们在当前的dev版本中完全实现此功能(8月3日,他们开始在实时环境中减少5个请求中的1个,如果他们没有签名,这对您来说是个坏消息R应用程序。

    这里是执行SOAP请求实际签名的主要部分的一个片段。

    class ClientOutputFilter : SoapFilter
    {
        // to store the AWS Access Key ID and corresponding Secret Key.
        String akid;
        String secret;
    
        // Constructor
        public ClientOutputFilter(String awsAccessKeyId, String awsSecretKey)
        {
            this.akid = awsAccessKeyId;
            this.secret = awsSecretKey;
        }
    
        // Here's the core logic:
        // 1. Concatenate operation name and timestamp to get StringToSign.
        // 2. Compute HMAC on StringToSign with Secret Key to get Signature.
        // 3. Add AWSAccessKeyId, Timestamp and Signature elements to the header.
        public override SoapFilterResult ProcessMessage(SoapEnvelope envelope)
        {
            var body = envelope.Body;
            var firstNode = body.ChildNodes.Item(0);
            String operation = firstNode.Name;
    
            DateTime currentTime = DateTime.UtcNow;
            String timestamp = currentTime.ToString("yyyy-MM-ddTHH:mm:ssZ");
    
            String toSign = operation + timestamp;
            byte[] toSignBytes = Encoding.UTF8.GetBytes(toSign);
            byte[] secretBytes = Encoding.UTF8.GetBytes(secret);
            HMAC signer = new HMACSHA256(secretBytes);  // important! has to be HMAC-SHA-256, SHA-1 will not work.
    
            byte[] sigBytes = signer.ComputeHash(toSignBytes);
            String signature = Convert.ToBase64String(sigBytes); // important! has to be Base64 encoded
    
            var header = envelope.Header;
            XmlDocument doc = header.OwnerDocument;
    
            // create the elements - Namespace and Prefix are critical!
            XmlElement akidElement = doc.CreateElement(
                AmazonHmacAssertion.AWS_PFX, 
                "AWSAccessKeyId", 
                AmazonHmacAssertion.AWS_NS);
            akidElement.AppendChild(doc.CreateTextNode(akid));
    
            XmlElement tsElement = doc.CreateElement(
                AmazonHmacAssertion.AWS_PFX,
                "Timestamp",
                AmazonHmacAssertion.AWS_NS);
            tsElement.AppendChild(doc.CreateTextNode(timestamp));
    
            XmlElement sigElement = doc.CreateElement(
                AmazonHmacAssertion.AWS_PFX,
                "Signature",
                AmazonHmacAssertion.AWS_NS);
            sigElement.AppendChild(doc.CreateTextNode(signature));
    
            header.AppendChild(akidElement);
            header.AppendChild(tsElement);
            header.AppendChild(sigElement);
    
            // we're done
            return SoapFilterResult.Continue;
        }
    }
    

    当进行实际的Web服务调用时,会像这样调用

    // create an instance of the serivce
    var api = new AWSECommerceService();
    
    // apply the security policy, which will add the require security elements to the
    // outgoing SOAP header
    var amazonHmacAssertion = new AmazonHmacAssertion(MY_AWS_ID, MY_AWS_SECRET);
    api.SetPolicy(amazonHmacAssertion.Policy());
    
    4 回复  |  直到 15 年前
        1
  •  7
  •   Brian Surowiec    15 年前

    我最后更新了使用wcf的代码,因为这就是我一直在研究的当前dev版本中的代码。然后我使用了一些发布在亚马逊论坛上的代码,但使其更易于使用。

    更新: 新的易于使用的代码,使您仍然可以使用所有配置设置

    在我之前发布的代码中,以及我在其他地方看到的,当创建服务对象时,其中一个构造函数重写用于告诉它使用HTTPS,为其提供HTTPS URL,并手动附加将执行签名的消息检查器。不使用默认构造函数的缺点是您无法通过配置文件配置服务。

    我已经重做了这段代码,这样您就可以继续使用默认的、无参数的、构造函数,并通过配置文件配置服务。这样做的好处是,您不必重新编译代码来使用它,也不必在部署后进行更改,例如对maxStringContentLength进行更改(这是导致此更改发生的原因,同时也发现了在代码中进行所有操作的缺点)。我还稍微更新了签名部分,这样您就可以告诉它要使用什么散列算法以及提取操作的regex。

    这两个变化是因为并非所有来自亚马逊的Web服务都使用相同的哈希算法,并且可能需要以不同的方式提取操作。这意味着您可以通过更改配置文件中的内容,为每种服务类型重用相同的代码。

    public class SigningExtension : BehaviorExtensionElement
    {
        public override Type BehaviorType
        {
            get { return typeof(SigningBehavior); }
        }
    
        [ConfigurationProperty("actionPattern", IsRequired = true)]
        public string ActionPattern
        {
            get { return this["actionPattern"] as string; }
            set { this["actionPattern"] = value; }
        }
    
        [ConfigurationProperty("algorithm", IsRequired = true)]
        public string Algorithm
        {
            get { return this["algorithm"] as string; }
            set { this["algorithm"] = value; }
        }
    
        [ConfigurationProperty("algorithmKey", IsRequired = true)]
        public string AlgorithmKey
        {
            get { return this["algorithmKey"] as string; }
            set { this["algorithmKey"] = value; }
        }
    
        protected override object CreateBehavior()
        {
            var hmac = HMAC.Create(Algorithm);
            if (hmac == null)
            {
                throw new ArgumentException(string.Format("Algorithm of type ({0}) is not supported.", Algorithm));
            }
    
            if (string.IsNullOrEmpty(AlgorithmKey))
            {
                throw new ArgumentException("AlgorithmKey cannot be null or empty.");
            }
    
            hmac.Key = Encoding.UTF8.GetBytes(AlgorithmKey);
    
            return new SigningBehavior(hmac, ActionPattern);
        }
    }
    
    public class SigningBehavior : IEndpointBehavior
    {
        private HMAC algorithm;
    
        private string actionPattern;
    
        public SigningBehavior(HMAC algorithm, string actionPattern)
        {
            this.algorithm = algorithm;
            this.actionPattern = actionPattern;
        }
    
        public void Validate(ServiceEndpoint endpoint)
        {
        }
    
        public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
        {
        }
    
        public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
        {
        }
    
        public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
        {
            clientRuntime.MessageInspectors.Add(new SigningMessageInspector(algorithm, actionPattern));
        }
    }
    
    public class SigningMessageInspector : IClientMessageInspector
    {
        private readonly HMAC Signer;
    
        private readonly Regex ActionRegex;
    
        public SigningMessageInspector(HMAC algorithm, string actionPattern)
        {
            Signer = algorithm;
            ActionRegex = new Regex(actionPattern);
        }
    
        public void AfterReceiveReply(ref Message reply, object correlationState)
        {
        }
    
        public object BeforeSendRequest(ref Message request, IClientChannel channel)
        {
            var operation = GetOperation(request.Headers.Action);
            var timeStamp = DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ssZ");
            var toSignBytes = Encoding.UTF8.GetBytes(operation + timeStamp);
            var sigBytes = Signer.ComputeHash(toSignBytes);
            var signature = Convert.ToBase64String(sigBytes);
    
            request.Headers.Add(MessageHeader.CreateHeader("AWSAccessKeyId", Helpers.NameSpace, Helpers.AWSAccessKeyId));
            request.Headers.Add(MessageHeader.CreateHeader("Timestamp", Helpers.NameSpace, timeStamp));
            request.Headers.Add(MessageHeader.CreateHeader("Signature", Helpers.NameSpace, signature));
    
            return null;
        }
    
        private string GetOperation(string request)
        {
            var match = ActionRegex.Match(request);
            var val = match.Groups["action"];
            return val.Value;
        }
    }
    

    要使用它,您不需要对现有代码进行任何更改,甚至可以根据需要将签名代码放入整个其他程序集中。您只需要这样设置配置部分(注意:版本号很重要,如果没有它匹配代码,将无法加载或运行)

    <system.serviceModel>
      <extensions>
        <behaviorExtensions>
          <add name="signer" type="WebServices.Amazon.SigningExtension, AmazonExtensions, Version=1.3.11.7, Culture=neutral, PublicKeyToken=null" />
        </behaviorExtensions>
      </extensions>
      <behaviors>
        <endpointBehaviors>
          <behavior name="AWSECommerceBehaviors">
            <signer algorithm="HMACSHA256" algorithmKey="..." actionPattern="\w:\/\/.+/(?&lt;action&gt;.+)" />
          </behavior>
        </endpointBehaviors>
      </behaviors>
      <bindings>
        <basicHttpBinding>
          <binding name="AWSECommerceServiceBinding" closeTimeout="00:01:00" openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00" allowCookies="false" bypassProxyOnLocal="false" hostNameComparisonMode="StrongWildcard" messageEncoding="Text" textEncoding="utf-8" transferMode="Buffered" useDefaultWebProxy="true" maxBufferSize="65536" maxBufferPoolSize="524288" maxReceivedMessageSize="65536">
            <readerQuotas maxDepth="32" maxStringContentLength="16384" maxArrayLength="16384" maxBytesPerRead="4096" maxNameTableCharCount="16384" />
            <security mode="Transport">
              <transport clientCredentialType="None" proxyCredentialType="None" realm="" />
              <message clientCredentialType="UserName" algorithmSuite="Default" />
            </security>
          </binding>
        </basicHttpBinding>
      </bindings>
      <client>
        <endpoint address="https://ecs.amazonaws.com/onca/soap?Service=AWSECommerceService" behaviorConfiguration="AWSECommerceBehaviors" binding="basicHttpBinding" bindingConfiguration="AWSECommerceServiceBinding" contract="WebServices.Amazon.AWSECommerceServicePortType" name="AWSECommerceServicePort" />
      </client>
    </system.serviceModel>
    
        2
  •  1
  •   Oren Trutner    15 年前

    嘿,布莱恩,我的应用程序也在处理同样的问题。我使用的是WSDL生成的代码——事实上,我今天再次生成它来确保最新的版本。我发现使用X509证书签名是最简单的路径。在我的安全带下进行了几分钟的测试,到目前为止它似乎工作正常。从本质上来说,你的改变来自:

    AWSECommerceService service = new AWSECommerceService();
    // ...then invoke some AWS call
    

    到:

    AWSECommerceService service = new AWSECommerceService();
    service.ClientCertificates.Add(X509Certificate.CreateFromCertFile(@"path/to/cert.pem"));
    // ...then invoke some AWS call
    

    在bytesblocks.com上发布的viper more details 包括如何获得Amazon为您生成的X509证书。

    编辑 :作为讨论 here 表示,这可能不会实际签署请求。会随着我的学习而发布。

    编辑 :这似乎根本没有签署请求。相反,它似乎需要一个HTTPS连接,并使用证书进行SSL客户端身份验证。ssl客户机身份验证是ssl中很少使用的功能。如果亚马逊产品广告API支持它作为认证机制,那就太好了!不幸的是,情况似乎并非如此。证据是双重的:(1)它不是 documented authentication schemes 和(2)您指定的证书并不重要。

    亚马逊公司甚至在2009年8月15日的最后期限宣布之后,仍然没有对请求进行身份验证,这也增加了一些困惑。这使得添加证书时的请求看起来正确通过,即使它可能不会添加任何值。

    看看BrianSurowiec的答案,找到一个可行的解决方案。我把这个答案留在这里是为了记录这个吸引人但显然失败的方法,因为我仍然可以在博客和亚马逊论坛上看到它的讨论。

        3
  •  0
  •   John Saunders    15 年前

    您可以使用 ProtectionLevel 属性。见 Understanding Protection Level .

        4
  •  0
  •   Brandon    15 年前

    签名的SOAP实现有点讨厌。我用PHP做的 http://www.apisigning.com/ .最后我发现的技巧是签名、awsaccesskey和时间戳参数需要放在SOAP头中。另外,签名只是操作+时间戳的散列,不需要包含任何参数。

    我不知道这怎么适合C,但我认为它可能有一些用处。