Frage

Die Amazon Product Advertising API (ehemals Amazon Associates Web Service oder Amazon AWS) hat eine neue Regel implementiert, die bis zum 15. August 2009 alle Web -Service -Anfragen für sie unterzeichnet werden müssen. Sie haben auf ihrer Website Beispielcode bereitgestellt, wie dies in C# mit REST und SOAP geht. Die Implementierung, die ich verwende, ist Seife. Sie können den Beispielcode finden hier, Ich schließe es nicht ein, weil es eine angemessene Menge gibt.

Das Problem, das ich habe, ist, dass ihr Beispielcode WSE 3 verwendet und unser aktueller Code WSE nicht verwendet. Weiß jemand Ich möchte jetzt nicht auf die WSE 3 -Sachen wechseln müssen, wenn ich nicht muss, da dieses Update eher ein kurzer Patch ist, um uns zu halten, bis wir dies in der aktuellen Dev -Version (August "vollständig implementieren können 3. Sie beginnen, 1 von 5 Anfragen in der Live -Umgebung zu fallen, wenn sie nicht unterschrieben sind, was für unsere Bewerbung eine schlechte Nachricht ist).

Hier ist ein Ausschnitt des Hauptabschnitts, der die tatsächliche Unterzeichnung der SOAP -Anfrage durchführt.

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

Und das wird so aufgerufen, wenn er den tatsächlichen Web -Service -Anruf tätigt

// 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());
War es hilfreich?

Lösung

Am Ende habe ich den Code aktualisiert, um WCF zu verwenden, da es in der aktuellen Entwicklerversion, an der ich gearbeitet habe, genau das ist. Dann habe ich einen Code verwendet, der in den Amazon -Foren veröffentlicht wurde, es aber ein wenig einfacher zu verwenden hat.

AKTUALISIEREN: Neu einfacher zu verwenden Code, mit dem Sie die Konfigurationseinstellungen für alles verwenden können

Im vorherigen Code, den ich gepostet habe, und was ich an anderer Stelle gesehen habe, wird ein der Konstruktor -Überschreibungen verwendet, um es zu sagen, dass er HTTPS verwendet, geben Sie ihm die HTTPS die Unterzeichnung. Der Untergang, um den Standardkonstruktor nicht zu verwenden, besteht darin, dass Sie die Möglichkeit verlieren, den Dienst über die Konfigurationsdatei zu konfigurieren.

Ich habe diesen Code seitdem überarbeitet, damit Sie weiterhin die Standard-, Parameter -Konstruktor -Konstruktor verwenden und den Dienst über die Konfigurationsdatei konfigurieren können. Das Benifit davon ist, dass Sie Ihren Code nicht neu kompilieren müssen, um diese zu verwenden, oder Änderungen vornehmen, sobald sie bereitgestellt wurden, z. B. zu MaxStringContentLength (was diese Änderung verursachte, und die Stürze für das alles im Code zu entdecken). . Ich habe auch den Unterschriftenteil ein wenig aktualisiert, sodass Sie ihm sagen können, was Hashing -Algorithmus verwendet werden soll, sowie den Regex zum Extrahieren der Aktion.

Diese beiden Änderungen liegen darauf, dass nicht alle Webdienste von Amazon denselben Hashing -Algorithmus verwenden und die Aktion möglicherweise unterschiedlich extrahiert werden muss. Dies bedeutet, dass Sie denselben Code für jeden Service -Typ wiederverwenden können, indem Sie das, was sich in der Konfigurationsdatei befindet, geändert hat.

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

Um dies zu verwenden, müssen Sie keine Änderungen an Ihrem vorhandenen Code vornehmen. Sie können den Signaturcode bei Bedarf sogar in eine ganz andere Baugruppe einfügen. Sie müssen nur den Konfigurationsabschnitt als SO einrichten (Hinweis: Die Versionsnummer ist wichtig, ohne dass der Code nicht geladen oder ausgeführt wird).

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

Andere Tipps

Hey Brian, ich habe es mit dem gleichen Problem in meiner App zu tun. Ich verwende den Code mit dem WSDL -generierten Code - tatsächlich habe ich ihn heute wieder generiert, um die neueste Version zu gewährleisten. Ich fand die Unterzeichnung mit einem X509 -Zertifikat den einfachsten Weg. Mit ein paar Minuten unter meinem Gürtel scheint es bisher in Ordnung zu funktionieren. Im Wesentlichen ändern Sie:

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

Zu:

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

Viper bei bytesblocks.com gepostet mehr Details, einschließlich des Erhaltens des X509 -Zertifikats, das Amazon für Sie generiert.

BEARBEITEN: als Diskussion hier zeigt an, dass dies die Anfrage möglicherweise nicht unterzeichnet. Werde posten, während ich mehr lerne.

BEARBEITEN: Dies scheint die Anfrage überhaupt nicht zu unterschreiben. Stattdessen erscheint es anscheinend eine HTTPS -Verbindung und verwendet das Zertifikat für die SSL -Client -Authentifizierung. Die SSL -Client -Authentifizierung ist eine selten verwendete Funktion von SSL. Es wäre schön gewesen, wenn die Amazon Product Advertising API sie als Authentifizierungsmechanismus unterstützt hätte! Leider scheint das nicht der Fall zu sein. Der Beweis ist zweifach: (1) Es ist nicht einer der der Dokumentierte Authentifizierungsschemata, und (2) spielt keine Rolle, welches Zertifikat Sie angeben.

Eine gewisse Verwirrung wird von Amazon hinzugefügt, die die Authentifizierung auf Anfragen immer noch nicht durchsetzen, selbst nachdem sie die Frist am 15. August 2009 verkündet hatten. Dadurch scheinen Anforderungen richtig zu bestehen, wenn das Zertifikat hinzugefügt wird, obwohl es möglicherweise keinen Wert hinzufügt.

Schauen Sie sich Brian Surowiecs Antwort auf eine Lösung an, die funktioniert. Ich verlasse diese Antwort hier, um den ansprechenden, aber anscheinend gescheiterten Ansatz zu dokumentieren, da ich sie immer noch in Blogs und Amazon -Foren sehen kann.

Sie können dies mit dem tun ProtectionLevel Attribute. Sehen Schutzniveau verstehen.

Die SOAP -Implementierung der Signatur ist freundlich. Ich habe es in PHP zur Verwendung gemacht http://www.apisigning.com/. Der Trick, den ich schließlich herausfand, war, dass die Parameter von Signature, Awsaccesskey und Zeitstempel in den Seifenkopfziel eingehen müssen. Außerdem ist die Signatur nur ein Hash des Operation + Timestamp und muss keine Parameter enthalten.

Ich bin mir nicht sicher, wie das in C#passt, dachte aber, dass es von irgendeiner Verwendung sein könnte

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top