문제

Amazon 제품 광고 API (이전 Amazon Associates 웹 서비스 또는 Amazon AWS)는 2009 년 8 월 15 일까지 모든 웹 서비스 요청에 서명 해야하는 새로운 규칙을 구현했습니다. 그들은 REST와 SOAP를 사용하여 C# 에서이 작업을 수행하는 방법을 보여주는 사이트에 샘플 코드를 제공했습니다. 내가 사용하는 구현은 SOAP입니다. 샘플 코드를 찾을 수 있습니다 여기, 나는 상당한 금액이 있기 때문에 그것을 포함하지 않습니다.

내가 가진 문제는 샘플 코드를 WSE 3 사용하고 현재 코드는 WSE를 사용하지 않는다는 것입니다. WSDL에서 자동 생성 된 코드를 사용 하여이 업데이트를 구현하는 방법을 아는 사람이 있습니까? 이 업데이트가 현재 Dev 버전 (8 월에서이를 완전히 구현할 수있을 때까지 우리를 붙잡을 수있는 빠른 패치가 더 빠른 이후로 WSE 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;
    }
}

그리고 실제 웹 서비스 호출을 할 때는 이렇게 호출됩니다.

// 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 버전에있는 것이기 때문입니다. 그런 다음 아마존 포럼에 게시 된 코드를 사용했지만 사용하기가 조금 쉬워졌습니다.

업데이트: 여전히 모든 것에 대한 구성 설정을 사용할 수있는 새로운 코드를 사용하기 쉽습니다.

내가 게시 한 이전 코드와 다른 곳에서 본 것, 서비스 객체가 생성되면 생성자 재정 중 하나가 HTTPS를 사용하도록 지시하고 HTTPS URL을 제공하고 수행 할 메시지 검사관을 수동으로 첨부하는 데 사용됩니다. 서명. 기본 생성자를 사용하지 않는 몰락은 구성 파일을 통해 서비스를 구성하는 기능을 잃는다는 것입니다.

이후이 코드를 다시 작성하여 기본, 매개 변수가없는 생성자를 계속 사용하고 구성 파일을 통해 서비스를 구성 할 수 있습니다. 이것의 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;
    }
}

이를 사용하려면 기존 코드를 변경할 필요가 없으므로 필요한 경우 사인 코드를 전체 다른 어셈블리에 넣을 수도 있습니다. 구성 섹션을 SO로 설정하면됩니다 (참고 : 버전 번호가 중요합니다. 코드와 일치하지 않으면 코드가로드되거나 실행되지 않습니다).

<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

bytesblocks.com의 Viper는 게시되었습니다 자세한 내용은, Amazon이 생성하는 X509 인증서를 얻는 방법을 포함하여.

편집하다: 토론으로 여기 실제로 요청에 서명하지 않을 수 있습니다. 더 많이 배우면 게시됩니다.

편집하다: 이것은 요청에 전혀 서명하지 않는 것 같습니다. 대신 HTTPS 연결이 필요한 것으로 보이며 SSL 클라이언트 인증에 인증서를 사용합니다. SSL 클라이언트 인증은 SSL의 드물게 사용되는 기능입니다. Amazon 제품 광고 API가 인증 메커니즘으로 지원한다면 좋았을 것입니다! 불행히도 그것은 사실이 아닌 것 같습니다. 증거는 두 가지입니다. (1) 그것은 하나가 아닙니다. 문서화 된 인증 체계, (2) 어떤 인증서를 지정하는지는 중요하지 않습니다.

Amazon은 2009 년 8 월 15 일 마감일을 선포 한 후에도 요청에 따라 여전히 인증을 시행하지 않습니다. 이렇게하면 인증서가 추가 될 때 요청이 올바르게 전달되는 것처럼 보이지만 값이 추가되지 않을 수도 있습니다.

작동하는 해결책에 대한 Brian Surowiec의 답변을보십시오. 블로그와 아마존 포럼에서 논의 된 것을 볼 수 있듯이 매력적이지만 분명히 실패한 접근 방식을 문서화하기 위해이 답을 남기고 있습니다.

당신은 이것을 사용하여 할 수 있습니다 ProtectionLevel 속성. 보다 보호 수준 이해.

서명의 비누 구현은 불쾌합니다. 나는 PHP에서 사용하기 위해 그것을했다 http://www.apisigning.com/. 내가 마침내 알아 낸 속임수는 서명, awsaccesskey 및 timestamp 매개 변수가 비누 헤더에 들어가야한다는 것입니다. 또한 서명은 작동 + 타임 스탬프의 해시 일 뿐이며 매개 변수를 포함 할 필요가 없습니다.

그것이 C#에 어떻게 적합한 지 잘 모르겠지만 약간의 사용일지도 모른다고 생각했습니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top