如何登录.NET亚马逊Web服务请求使用SOAP和不WSE [英] How to sign an Amazon web service request in .NET with SOAP and without WSE

查看:176
本文介绍了如何登录.NET亚马逊Web服务请求使用SOAP和不WSE的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

亚马逊商品广告API(原亚马逊联盟的Web服务或Amazon AWS)已经实施了新的规则,它是2009年8月15号的所有Web服务请求的人必须签字。他们提供的样本code在其网站上展示了如何使用REST和SOAP在C#中做到这一点。我使用的实现SOAP。你可以找到样本code rel="nofollow">,我不包括它,因为有相当数量。

我遇到的问题是他们的样品code使用WSE 3和我们现在的code不使用WSE。有谁知道如何实现此更新只使用自动生成的code从WSDL?我想不必切换到了WSE 3的东西,现在,如果我没有,因为此更新更多的是一种快速补丁把我们联系过,直到我们完全可以实现这个在目前的开发版本(八月第三,他们已经开始下降1 5的要求,在现场环境中,如果他们不签这是坏消息,我们的应用程序)。

下面是一条什么SOAP请求的实际签约的主要部分的片段。

 类ClientOutputFilter:SoapFilter
{
    //存储AWS访问密钥ID和对应的密钥。
    字符串AKID;
    字符串秘密;

    //构造函数
    公共ClientOutputFilter(字符串awsAccessKeyId,字符串awsSecretKey)
    {
        this.akid = awsAccessKeyId;
        this.secret = awsSecretKey;
    }

    //这里的核心逻辑:
    让StringToSign // 1.连接操作名称和时间戳。
    // 2.计算HMAC上StringToSign用密钥来获得签名。
    // 3.添加AWSAccessKeyId,时间戳和标志性元素的头。
    公众覆盖SoapFilterResult ProcessMessage的(的SoapEnvelope信封)
    {
        VAR体= envelope.Body;
        变种firstNode = body.ChildNodes.Item(0);
        字符串操作= firstNode.Name;

        日期时间currentTime的= DateTime.UtcNow;
        串时间戳= currentTime.ToString(YYYY-MM-DDTHH:毫米:SSZ);

        字符串toSign =操作+时间戳记;
        byte []的toSignBytes = Encoding.UTF8.GetBytes(toSign);
        byte []的secretBytes = Encoding.UTF8.GetBytes(秘密);
        HMAC签名=新HMACSHA256(secretBytes); // 重要!已经为HMAC-SHA-256,SHA-1将无法正常工作。

        byte []的sigBytes = signer.ComputeHash(toSignBytes);
        特征码= Convert.ToBase64String(sigBytes); // 重要!已被Base64的EN codeD

        VAR标题= envelope.Header;
        XmlDocument的DOC = header.OwnerDocument;

        //创建的元素 - 命名空间和preFIX是至关重要的!
        的XmlElement akidElement = doc.CreateElement(
            AmazonHmacAssertion.AWS_PFX,
            AWSAccessKeyId
            AmazonHmacAssertion.AWS_NS);
        akidElement.AppendChild(doc.CreateTextNode(AKID));

        的XmlElement tsElement = doc.CreateElement(
            AmazonHmacAssertion.AWS_PFX,
            时间戳,
            AmazonHmacAssertion.AWS_NS);
        tsElement.AppendChild(doc.CreateTextNode(时间戳));

        的XmlElement sigElement = doc.CreateElement(
            AmazonHmacAssertion.AWS_PFX,
            签名,
            AmazonHmacAssertion.AWS_NS);
        sigElement.AppendChild(doc.CreateTextNode(签字));

        header.AppendChild(akidElement);
        header.AppendChild(tsElement);
        header.AppendChild(sigElement);

        // 做完了
        返回SoapFilterResult.Continue;
    }
}
 

和被调用这样做实际的Web服务调用时

  //创建中的service的一个实例
VAR API =新AWSECommerceService();

//应用安全策略,这将增加需要安全元素的
//传出的SOAP消息头
VAR amazonHmacAssertion =新AmazonHmacAssertion(MY_AWS_ID,MY_AWS_SECRET);
api.SetPolicy(amazonHmacAssertion.Policy());
 

解决方案

我结束了更新code使用WCF,因为那是它是在当前开发的版本,我一直工作在什么

。然后我用那个被张贴在亚马逊论坛约code,但使它成为更易于使用。

更新:新的更容易使用code,让您仍然使用的一切的配置设置

在previous code我张贴,以及我在其他地方看到的,当服务对象被创建的构造覆盖的一个是用来告诉它使用HTTPS,给它的HTTPS URL和手动附加消息检查将进行数字签名。倒台不使用默认的构造函数是你失去了通过配置文件来配置服务的能力。

因为我已经重做这个code,所以你可以继续使用默认的,无参数,构造,并通过配置文件配置服务。这样做的benifit是,你不必重新编译code使用这个,或者使一旦部署如maxStringContentLength(这是什么原因造成这种变化要发生,以及发现挫折做这一切的变化在code)。我也更新了部分签署了一下,这样的方式,你可以告诉它使用的哈希算法以及正则表达式提取的行动。

这两个变化是由于亚马逊并不是所有的Web服务中使用的相同的哈希算法和行动可能需要提取不同。这意味着你可以通过改变什么是在配置文件中重复使用相同的code为每种服务类型。

 公共类SigningExtension:BehaviorExtensionElement
{
    公众覆盖类型BehaviorType
    {
        {返回的typeof(SigningBehavior); }
    }

    [的ConfigurationProperty(actionPattern,IsRequired =真)
    公共字符串ActionPattern
    {
        {返回这个[actionPattern]作为串; }
        集合{这个[actionPattern] =值; }
    }

    [的ConfigurationProperty(算法,IsRequired =真)
    公共字符串算法
    {
        {返回这个[算法]作为串; }
        集合{这个[算法] =价值; }
    }

    [的ConfigurationProperty(algorithmKey,IsRequired =真)
    公共字符串AlgorithmKey
    {
        {返回这个[algorithmKey]作为串; }
        集合{这个[algorithmKey] =值; }
    }

    保护覆盖对象CreateBehavior()
    {
        VAR HMAC = HMAC.Create(算法);
        如果(HMAC == NULL)
        {
            抛出新的ArgumentException(的String.Format(({0})不支持类型的算法。算法));
        }

        如果(string.IsNullOrEmpty(AlgorithmKey))
        {
            抛出新的ArgumentException(AlgorithmKey不能为空或为空);
        }

        hmac.Key = Encoding.UTF8.GetBytes(AlgorithmKey);

        返回新SigningBehavior(HMAC,ActionPattern);
    }
}

公共类SigningBehavior:IEndpointBehavior
{
    私人HMAC算法;

    私人字符串actionPattern;

    公共SigningBehavior(HMAC算法,串actionPattern)
    {
        this.algorithm =算法;
        this.actionPattern = actionPattern;
    }

    公共无效验证(ServiceEndpoint端点)
    {
    }

    公共无效AddBindingParameters(ServiceEndpoint终点,BindingParameterCollection bindingParameters)
    {
    }

    公共无效ApplyDispatchBehavior(ServiceEndpoint终点,EndpointDispatcher endpointDispatcher)
    {
    }

    公共无效ApplyClientBehavior(ServiceEndpoint终点,ClientRuntime clientRuntime)
    {
        clientRuntime.MessageInspectors.Add(新SigningMessageInspector(算法,actionPattern));
    }
}

公共类SigningMessageInspector:IClientMessageInspector
{
    私人只读HMAC签名者;

    私人只读正则表达式ActionRegex;

    公共SigningMessageInspector(HMAC算法,串actionPattern)
    {
        签名者=算法;
        ActionRegex ​​=新的正则表达式(actionPattern);
    }

    公共无效AfterReceiveReply(参考消息回复,反对correlationState)
    {
    }

    公共对象BeforeSendRequest(参考消息请求,IClientChannel频道)
    {
        变种运行= GetOperation(request.Headers.Action);
        变种的timeStamp = DateTime.UtcNow.ToString(YYYY-MM-DDTHH:毫米:SSZ);
        VAR toSignBytes = Encoding.UTF8.GetBytes(操作+时间戳);
        VAR sigBytes = Signer.ComputeHash(toSignBytes);
        VAR签名= Convert.ToBase64String(sigBytes);

        request.Headers.Add(MessageHeader.CreateHeader(AWSAccessKeyId,Helpers.NameSpace,Helpers.AWSAccessKeyId));
        request.Headers.Add(MessageHeader.CreateHeader(时间戳,Helpers.NameSpace,时间戳));
        request.Headers.Add(MessageHeader.CreateHeader(签名,Helpers.NameSpace,签字));

        返回null;
    }

    私人字符串GetOperation(字符串请求)
    {
        VAR匹配= ActionRegex.Match(要求);
        VAR VAL = match.Groups [行动];
        返回val.Value;
    }
}
 

要使用这个,你不需要做任何修改现有的code,你甚至可以把签约code在整体的其他组件,如果需要的话。你只需要设置的配置部分为左右(注:版本号是很重要的,没有它匹配code将不会加载或运行)

 < system.serviceModel>
  <扩展>
    < behaviorExtensions>
      <添加名称=签名者类型=WebServices.Amazon.SigningExtension,AmazonExtensions,版本= 1.3.11.7,文化=中性公钥=空/>
    < / behaviorExtensions>
  < /扩展>
  <行为>
    < endpointBehaviors>
      <行为NAME =AWSECommerceBehaviors>
        <签名算法=HMACSHA256algorithmKey =...actionPattern =\ W:\ / \ /.+/(?&放大器; LT;动作&放大器; GT; +)/>
      < /行为>
    < / endpointBehaviors>
  < /行为>
  <绑定>
    < basicHttpBinding的>
      <绑定名称=AWSECommerceServiceBindingcloseTimeout =00:01:00openTimeout =00:01:00receiveTimeout =00:10:00的SendTimeout =00:01:00allowCookies =假bypassProxyOnLocal =假hostNameComparisonMode =StrongWildcardmessageEncoding =文本textEncoding =UTF-8transferMode =缓冲useDefaultWebProxy =真maxBufferSize =65536maxBufferPoolSize =524288maxReceivedMessageSize =65536>
        < readerQuotas MAXDEPTH =32maxStringContentLength =16384maxArrayLength =16384maxBytesPerRead =4096maxNameTableCharCount =16384/>
        <安全模式=运输>
          <交通运输clientCredentialType =无proxyCredentialType =无的境界=/>
          <消息clientCredentialType =用户名algorithmSuite =默认/>
        < /安全>
      < /装订>
    < / basicHttpBinding的>
  < /绑定>
  <客户端>
    <端点地址=htt​​ps://ecs.amazonaws.com/onca/soap?Service=AWSECommerceServicebehaviorConfiguration =AWSECommerceBehaviors绑定=basicHttpBinding的bindingConfiguration =AWSECommerceServiceBinding合同=WebServices.Amazon.AWSECommerceServicePortTypeNAME = AWSECommerceServicePort/>
  < /客户>
< /system.serviceModel>
 

The Amazon Product Advertising API (formerly Amazon Associates Web Service or Amazon AWS) has implemented a new rule which is by August 15th 2009 all web service requests to them must be signed. They have provided sample code on their site showing how to do this in C# using both REST and SOAP. The implementation that I’m using is SOAP. You can find the sample code here, I’m not including it because there is a fair amount.

The problem I’m having is their sample code uses WSE 3 and our current code doesn’t use WSE. Does anyone know how to implement this update with just using the auto generated code from the WSDL? I’d like to not have to switch over to the WSE 3 stuff right now if I don’t have to since this update is more of a quick patch to hold us over until we can fully implement this in the current dev version (August 3rd they’re starting to drop 1 in 5 requests, in the live environment, if they aren’t signed which is bad news for our application).

Here’s a snippet of the main portion that does the actual signing of the SOAP request.

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

And that gets called like this when making the actual web service call

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

解决方案

I ended up updating the code to use WCF since that's what it is in the current dev version I've been working on. Then I used some code that was posted on the Amazon forums, but made it a little easier to use.

UPDATE: new easier to use code that lets you still use the config settings for everything

In the previous code I posted, and what I've seen elsewhere, when the service object is created one of the constructor overrides is used to tell it to use HTTPS, give it the HTTPS url and to manually attach the message inspector that will do the signing. The downfall to not using the default constructor is you lose the ability to configure the service via the config file.

I've since redone this code so you can continue to use the default, parameterless, constructor and configure the service via the config file. The benifit of this is you don't have to recompile your code to use this, or make changes once deployed such as to maxStringContentLength (which is what caused this change to take place as well as discover the downfalls to doing it all in code). I also updated the signing part a bit so that way you can tell it what hashing algorithm to use as well as the regex for extracting the Action.

These two changes are because not all web services from Amazon use the same hashing algorithm and the Action might need to be extracted differently. This means you can reuse the same code for each service type just by changing what’s in the config file.

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

To use this you don't need to make any changes to your existing code, you can even put the signing code in a whole other assembly if need be. You just need to set up the config section as so (note: the version number is important, without it matching the code will not load or run)

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

这篇关于如何登录.NET亚马逊Web服务请求使用SOAP和不WSE的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆