如何使用SOAP和不使用WSE在.NET中签署Amazon Web服务请求

亚马逊产品广告API(以前称为Amazon Associates Web服务或亚马逊AWS)已实施新规则,到2009年8月15日,必须对所有Web服务请求进行签名。 他们在他们的网站上提供了示例代码,展示了如何使用REST和SOAP在C#中执行此操作。 我正在使用的实现是SOAP。 你可以在这里找到示例代码,我不包括它,因为有相当数量。

我遇到的问题是他们的示例代码使用WSE 3,我们当前的代码不使用WSE。 有没有人知道如何使用WSDL中自动生成的代码来实现此更新? 如果我不需要,我现在不必切换到WSE 3的东西,因为这个更新更像是一个快速的补丁,让我们能够完全实现这个目前的开发版本(8月) 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()); 

我最终更新了代码以使用WCF,因为这就是我一直在研究的当前开发版本中的内容。 然后我使用了一些在亚马逊论坛上发布的代码,但使它更容易使用。

更新:新的更易于使用的代码,让您仍然可以使用配置设置的一切

在我之前发布的代码中,以及我在别处看到的内容,当创建服务对象时,其中一个构造函数覆盖用于告诉它使用HTTPS,给它提供HTTPS URL并手动附加将执行的消息检查器签约。 不使用默认构造函数的缺点是您失去了通过配置文件配置服务的能力。

我已经重做了这段代码,因此您可以继续使用默认的无参数构造函数,并通过配置文件配置服务。 这样做的好处是你不必重新编译你的代码来使用它,或者在部署之后进行更改,例如maxStringContentLength(导致这种改变发生的原因,以及发现在代码中完成所有操作的缺点) 。 我还更新了签名部分,以便您可以告诉它使用什么哈希算法以及提取Action的正则表达式。

这两个更改是因为并非所有来自Amazon的Web服务都使用相同的散列算法,并且可能需要以不同方式提取Action。 这意味着只需更改配置文件中的内容,即可为每种服务类型重用相同的代码。

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

要使用它,您不需要对现有代码进行任何更改,如果需要,您甚至可以将签名代码放在整个其他程序集中。 您只需要设置配置部分(注意:版本号很重要,不匹配代码将不会加载或运行)

                             

嘿Brian,我在我的应用程序中处理同样的问题。 我正在使用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客户端身份validation。 SSL客户端身份validation是SSL的一种不常用的function。 如果亚马逊产品广告API支持它作为身份validation机制,那就太好了! 不幸的是,似乎并非如此。 证据有两个:(1)它不是记录的认证方案之一 ,(2)您指定的证书无关紧要。

亚马逊甚至在他们宣布2009年8月15日截止日期之后仍然没有对请求进行身份validation。 这使得添加证书时请求似乎正确传递,即使它可能不会添加任何值。

看看Brian Surowiec的答案,找到有效的解决方案。 我在这里留下这个答案来记录这个吸引人但却显然失败的方法,因为我仍然可以在博客和亚马逊论坛中看到它。

您可以使用ProtectionLevel属性执行此操作。 请参阅了解保护级别 。

签名的肥皂实现有点令人讨厌。 我用PHP在http://www.apisigning.com/上使用它。 我最终想到的技巧是Signature,AWSAccessKey和Timestamp参数需要放在SOAP头中。 此外,Signature只是Operation +时间戳的哈希值,不需要包含任何参数。

我不确定它是如何适合C#的,但是认为它可能有用