3

Amazon Product Advertising API(以前のAmazon Associates WebServiceまたはAmazonAWS)は、2009年8月15日までにすべてのWebサービスリクエストに署名する必要があるという新しいルールを実装しました。彼らは、RESTとSOAPの両方を使用してC#でこれを行う方法を示すサンプルコードをサイトに提供しています。私が使用している実装はSOAPです。サンプルコードはここにありますが、かなりの量があるため、ここには含めていません。

私が抱えている問題は、サンプルコードがWSE 3を使用しており、現在のコードがWSEを使用していないことです。WSDLから自動生成されたコードを使用するだけでこの更新を実装する方法を知っている人はいますか?このアップデートは、現在の開発バージョン(8月)で完全に実装できるようになるまで、私たちを引き留めるためのより迅速なパッチであるため、必要がなければ、今すぐWSE3のものに切り替える必要はありません。 3番目に、署名されていない場合、ライブ環境で5つのリクエストのうち1つをドロップし始めています。これは、アプリケーションにとって悪いニュースです)。

これは、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

4 に答える 4

7

私が取り組んでいる現在の開発バージョンでは、WCFを使用するようにコードを更新することになりました。次に、Amazonフォーラムに投稿されたコードを使用しましたが、少し使いやすくしました。

更新:すべての構成設定を引き続き使用できる、より使いやすい新しいコード

私が投稿した前のコードと他の場所で見たものでは、サービスオブジェクトが作成されるときに、コンストラクターオーバーライドの1つを使用して、HTTPSを使用するように指示し、HTTPS URLを指定し、メッセージインスペクターを手動でアタッチします。署名。デフォルトのコンストラクターを使用しないことの欠点は、構成ファイルを介してサービスを構成する機能を失うことです。

それ以来、このコードをやり直したので、デフォルトのパラメーターなしのコンストラクターを引き続き使用し、構成ファイルを介してサービスを構成できます。これの利点は、これを使用するためにコードを再コンパイルしたり、maxStringContentLengthなどの展開後に変更を加えたりする必要がないことです(これにより、この変更が行われ、すべてをコードで行うことの欠点がわかります) 。また、署名部分を少​​し更新して、使用するハッシュアルゴリズムと、アクションを抽出するための正規表現を指定できるようにしました。

これらの2つの変更は、Amazonのすべての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>
于 2009-08-01T16:13:19.740 に答える
1

ブライアンさん、アプリで同じ問題を扱っています。私は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は、Amazonが生成するX509証明書を取得する方法など、詳細を投稿しました。

編集ここでの説明が示すように、これは実際にはリクエストに署名しない可能性があります。詳細がわかり次第投稿します。

編集:これはリクエストにまったく署名していないようです。代わりに、https接続が必要であるように見え、SSLクライアント認証に証明書を使用します。SSLクライアント認証は、SSLの使用頻度の低い機能です。Amazon製品広告APIが認証メカニズムとしてそれをサポートしていたら良かったでしょう!残念ながら、そうではないようです。証拠は2つあります。(1)文書化された認証スキームの1つではないこと、および(2)指定する証明書は重要ではないことです。

Amazonは、2009年8月15日の期限を宣言した後も、リクエストに対して認証を実施していないため、混乱が生じています。これにより、証明書が追加されたときに、値が追加されない場合でも、要求が正しく通過したように見えます。

効果的な解決策については、BrianSurowiecの回答をご覧ください。ブログやAmazonフォーラムで議論されているのを見ることができるので、魅力的であるが明らかに失敗したアプローチを文書化するために、この回答をここに残しておきます。

于 2009-07-30T05:51:15.643 に答える
0

ProtectionLevelこれは、属性を使用して行うことができます。保護レベルについてを参照してください。

于 2009-07-30T05:43:18.157 に答える
0

署名の石鹸の実装はちょっと厄介です。私はhttp://www.apisigning.com/で使用するためにPHPでそれを行いました。私が最終的に理解したトリックは、Signature、AWSAccessKey、およびTimestampパラメーターをSOAPヘッダーに含める必要があるということでした。また、署名は操作+タイムスタンプのハッシュであり、パラメータを含める必要はありません。

それがC#にどのように適合するかはわかりませんが、ある程度役立つかもしれないと思いました

于 2009-08-27T21:03:46.427 に答える