كيفية التوقيع على طلب خدمة ويب Amazon في .NET مع الصابون وبدون WSE

StackOverflow https://stackoverflow.com/questions/1204191

سؤال

قامت Amazon Product Advertising API (خدمة الويب Amazon Associates أو Amazon AWS سابقًا) بتنفيذ قاعدة جديدة بحلول 15 أغسطس 2009 يجب توقيع جميع طلبات خدمة الويب إليها. لقد قدموا عينة رمز على موقعهم يوضح كيفية القيام بذلك في C# باستخدام كل من الراحة والصابون. التنفيذ الذي أستخدمه هو الصابون. يمكنك العثور على رمز نموذج هنا, ، أنا لا أدرجها لأن هناك مبلغًا لا بأس به.

المشكلة التي أواجهها هي أن نموذج الرمز الخاص بهم يستخدم WSE 3 ولا يستخدم الكود الحالي لدينا WSE. هل يعرف أي شخص كيفية تنفيذ هذا التحديث من خلال فقط استخدام الرمز الذي تم إنشاؤه التلقائي من WSDL؟ أود ألا أضطر إلى التبديل إلى أشياء WSE 3 في الوقت الحالي إذا لم يكن عليّ أن يكون هذا التحديث أكثر من تصحيح سريع للاحتفاظ بنا حتى نتمكن من تنفيذ هذا بالكامل في إصدار DEV الحالي (أغسطس الثالث بدأوا في إسقاط 1 من كل 5 طلبات ، في البيئة الحية ، إذا لم يتم تسجيلهم ، فهذا خبر سيء لتطبيقنا).

إليك مقتطف من الجزء الرئيسي الذي يقوم بالتوقيع الفعلي لطلب الصابون.

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

وهذا ما يسمى مثل هذا عند إجراء مكالمة خدمة الويب الفعلية

// 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());
هل كانت مفيدة؟

المحلول

انتهى بي الأمر بتحديث الكود لاستخدام WCF لأن هذا هو ما هو في إصدار DEV الحالي الذي كنت أعمل عليه. ثم استخدمت بعض التعليمات البرمجية التي تم نشرها على منتديات Amazon ، لكنني جعلت الأمر أسهل قليلاً في الاستخدام.

تحديث: جديد أسهل لاستخدام التعليمات البرمجية التي تتيح لك استخدام إعدادات التكوين لكل شيء

في الكود السابق الذي قمت بنشره ، وما رأيته في مكان آخر ، عندما يتم إنشاء كائن الخدمة ، يتم استخدام أحد تجاوزات المُنشأة لإخباره باستخدام HTTPS ، واعطه عنوان URL HTTPS ولتوصيل مفتش الرسائل يدويًا الذي سيفعل ذلك التوقيع. إن السقوط لعدم استخدام المُنشئ الافتراضي هو أن تفقد القدرة على تكوين الخدمة عبر ملف التكوين.

لقد قمت منذ إعادة تشغيل هذا الرمز حتى تتمكن من متابعة استخدام المُنشئ الافتراضي والمعلمة والمعلمة وتكوين الخدمة عبر ملف التكوين. إن benifit من هذا هو أنك لست مضطرًا إلى إعادة ترجمة الكود الخاص بك لاستخدام هذا ، أو إجراء تغييرات بمجرد نشرها مثل MaxStringContentLength (وهو ما تسبب في حدوث هذا التغيير وكذلك اكتشاف السقوط للقيام بذلك في الكود) . لقد قمت أيضًا بتحديث جزء التوقيع قليلاً حتى تتمكن من إخباره بما لاستخدام خوارزمية التجزئة وكذلك Regex لاستخراج الإجراء.

هذان التغييران يرجعان إلى أن جميع خدمات الويب من Amazon تستخدم نفس خوارزمية التجزئة وقد تحتاج الإجراء إلى استخراجها بشكل مختلف. هذا يعني أنه يمكنك إعادة استخدام نفس الرمز لكل نوع خدمة فقط عن طريق تغيير ما هو موجود في ملف التكوين.

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>

نصائح أخرى

مرحبًا براين ، أنا أتعامل مع نفس المشكلة في تطبيقي. أنا أستخدم رمز 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

المنشور Viper at BytesBlocks.com المزيد من التفاصيل, ، بما في ذلك كيفية الحصول على شهادة X509 التي تنشئها Amazon لك.

تعديل: كمناقشة هنا يشير إلى أن هذا قد لا يوقع بالفعل على الطلب. سوف تنشر كما أتعلم المزيد.

تعديل: لا يبدو أن هذا يوقع على الطلب على الإطلاق. بدلاً من ذلك ، يبدو أنه يتطلب اتصال HTTPS ، ويستخدم الشهادة لمصادقة عميل SSL. مصادقة عميل SSL هي ميزة غير متكررة من SSL. كان من الجيد لو دعمها واجهة برمجة تطبيقات Amazon Product Api كآلية مصادقة! لسوء الحظ ، لا يبدو أن هذا هو الحال. الدليل ذو شقين: (1) ليس واحدًا من مخططات المصادقة الموثقة, و (2) لا يهم الشهادة التي تحددها.

يضاف بعض الالتباس من قبل Amazon لا تزال لا تنفذ المصادقة على الطلبات حتى بعد إعلانها في الموعد النهائي في 15 أغسطس 2009. هذا يجعل الطلبات يبدو أنها تمر بشكل صحيح عند إضافة الشهادة ، على الرغم من أنها قد لا تضيف أي قيمة.

انظر إلى إجابة براين سوروسيك عن حل يعمل. سأترك هذه الإجابة هنا لتوثيق النهج الجذاب ولكن الفاشل على ما يبدو ، حيث لا يزال بإمكاني رؤيته تمت مناقشته في المدونات ومنتديات Amazon.

يمكنك القيام بذلك باستخدام ProtectionLevel صفات. نرى فهم مستوى الحماية.

تنفيذ الصابون للتوقيع هو لطيف. لقد فعلت ذلك في PHP للاستخدام على http://www.apisigning.com/. كانت الحيلة التي اكتشفتها أخيرًا هي أن معلمات التوقيع ، AWSAccesskey ، ومرات الطابع الزمني تحتاج إلى الذهاب في رأس الصابون. أيضًا ، التوقيع هو مجرد تجزئة للعملية + الطابع الزمني ، ولا يحتاج إلى تضمين أي معلمات.

لست متأكدًا من كيفية تناسب ذلك مع C#، لكن اعتقد أنه قد يكون مفيدًا

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top