【问题标题】:Amazon Product Advertising API Signature in iOSiOS 中的亚马逊产品广告 API 签名
【发布时间】:2012-06-12 00:09:50
【问题描述】:

我正在尝试在我的 iOS 应用程序中访问亚马逊的产品广告 API。创建签名似乎是困难的部分。在此页面上:

http://docs.amazonwebservices.com/AWSECommerceService/latest/DG/rest-signature.html

它说“使用 SHA256 哈希算法计算符合 RFC 2104 的 HMAC”。 Amazon 还提供了一个 java 类来为您执行此操作:

http://docs.amazonwebservices.com/AWSECommerceService/latest/DG/AuthJavaSampleSig2.html

有人知道我如何在 Objective-C 中做到这一点吗?我查看了 AWS iOS SDK,但它似乎不包含产品广告 API。

【问题讨论】:

    标签: objective-c ios amazon-web-services amazon amazon-product-api


    【解决方案1】:

    实际上,AWS iOS SDK DID 有一个静态方法来处理所有身份验证情况。 也许你应该看看AmazonAuthUtils.h

    +(NSString *)HMACSign:(NSData *)data withKey:(NSString *)key usingAlgorithm:(CCHmacAlgorithm)algorithm;
    +(NSData *)sha256HMac:(NSData *)data withKey:(NSString *)key;
    

    可以在文档中找到:http://docs.amazonwebservices.com/AWSiOSSDK/latest/Classes/AmazonAuthUtils.html

    【讨论】:

    • 该链接不再有效,AWS 框架的文档不再包含或提及任何类似于 AmazonAuthUtils 的内容。例如,这份 100 页的文档没有提及“签名”一词。有更新版本吗?
    【解决方案2】:

    只是为 camelcc 的出色观察添加一点。这确实适用于签署对亚马逊产品广告 API 的请求。我不得不搞砸一点才能让它工作。

    安装 SDK 并#import <AWSiOSSDK/AmazonAuthUtils.h>

    首先,您必须按照亚马逊文档将请求字符串组织成正确的顺序。我发现此页面在解释如何订购请求方面非常有用

    http://skilldrick.co.uk/2010/02/amazon-product-information-via-amazon-web-services/

    注意字符串中需要换行符,我的无符号字符串看起来像这样

    @"GET\necs.amazonaws.com\n/onca/xml\nAWSAccessKeyId=<ACCESS_KEY_ID>&AssociateTag=<ASSOCIATE_ID>&Keywords=harry%20potter&Operation=ItemSearch&SearchIndex=Books&Service=AWSECommerceService&Timestamp=2012-07-03T10%3A52%3A21.000Z&Version=2011-08-01"

    任何地方都没有空格,但\n 字符在正确的位置。像这样将其转换为NSData

    NSData *dataToSign = [unsignedString dataUsingEncoding:NSUTF8StringEncoding];

    然后调用

    [AmazonAuthUtils HMACSign:dataToSign withKey:SECRET_KEY usingAlgorithm:kCCHmacAlgSHA256]

    这会将您的签名返回为NSString。您需要对其进行 URL 编码(即用 %0x 符号交换非法/不安全字符(即 '=' 转换为 '%3D'))

    完成后,将其粘贴在您的请求中,希望您一切顺利!

    【讨论】:

    • 嘿,我在生成签名时遇到问题。我正在遵循您提到的相同方法。你能帮我解决这个问题吗? GET ecs.amazonaws.com /onca/xml AWSAccessKeyId=&AssociateTag=&Keywords=harry potter&Operation=ItemSearch&SearchIndex=Books&Service=AWSECommerceService&Timestamp=2012-07-27T11%3A12%3A46Z&Version=2011-08-01
    • 你把 '/n' 换行符放在我有的地方了吗?
    • 我解决了这个问题,你的回答对我帮助很大...谢谢!为此 +1
    • 只是添加到这个。要正确获取时间戳: NSDate *currentTime = [NSDate date]; NSString *timestamp = [currentTime stringWithISO8601Format]; NSString *timestampAmazon = [timestamp stringWithURLEncoding];
    • 今天的AWSCore及相关SDK中的等价物是什么?我没有看到任何提供相同功能的函数或框架。
    【解决方案3】:

    查看我的亚马逊产品广告客户https://github.com/m1entus/RWMAmazonProductAdvertisingManager

    一些带有请求序列化的代码:

    NSString * const RWMAmazonProductAdvertisingStandardRegion = @"webservices.amazon.com";
    NSString * const RWMAmazonProductAdvertisingAWSAccessKey = @"AWSAccessKeyId";
    NSString * const RWMAmazonProductAdvertisingTimestampKey = @"Timestamp";
    NSString * const RWMAmazonProductAdvertisingSignatureKey = @"Signature";
    NSString * const RWMAmazonProductAdvertisingVersionKey = @"Version";
    NSString * const RWMAmazonProductAdvertisingCurrentVersion = @"2011-08-01";
    
    NSData * RWMHMACSHA256EncodedDataFromStringWithKey(NSString *string, NSString *key) {
        NSData *data = [string dataUsingEncoding:NSASCIIStringEncoding];
        CCHmacContext context;
        const char *keyCString = [key cStringUsingEncoding:NSASCIIStringEncoding];
    
        CCHmacInit(&context, kCCHmacAlgSHA256, keyCString, strlen(keyCString));
        CCHmacUpdate(&context, [data bytes], [data length]);
    
        unsigned char digestRaw[CC_SHA256_DIGEST_LENGTH];
        NSUInteger digestLength = CC_SHA256_DIGEST_LENGTH;
    
        CCHmacFinal(&context, digestRaw);
    
        return [NSData dataWithBytes:digestRaw length:digestLength];
    }
    
    NSString * RWMISO8601FormatStringFromDate(NSDate *date) {
        NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
        [dateFormatter setTimeZone:[NSTimeZone timeZoneWithName:@"GMT"]];
        [dateFormatter setDateFormat:@"YYYY-MM-dd'T'HH:mm:ss'Z'"];
        [dateFormatter setLocale:[[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"]];
    
        return [dateFormatter stringFromDate:date];
    }
    
    NSString * RWMBase64EncodedStringFromData(NSData *data) {
    
    #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 70000
        return [data base64EncodedStringWithOptions:0];
    #else
        return [data base64Encoding];
    #endif
    
    }
    
    //http://docs.aws.amazon.com/AWSECommerceService/latest/DG/rest-signature.html
    
    - (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request
                                   withParameters:(id)parameters
                                            error:(NSError * __autoreleasing *)error
    {
        NSParameterAssert(request);
    
        NSMutableURLRequest *mutableRequest = [request mutableCopy];
    
        if (self.accessKey && self.secret) {
            NSMutableDictionary *mutableParameters = [parameters mutableCopy];
            NSString *timestamp = RWMISO8601FormatStringFromDate([NSDate date]);
    
            if (!mutableParameters[RWMAmazonProductAdvertisingAWSAccessKey]) {
                [mutableParameters setObject:self.accessKey forKey:RWMAmazonProductAdvertisingAWSAccessKey];
            }
            mutableParameters[RWMAmazonProductAdvertisingVersionKey] = RWMAmazonProductAdvertisingCurrentVersion;
            mutableParameters[RWMAmazonProductAdvertisingTimestampKey] = timestamp;
    
            NSMutableArray *canonicalStringArray = [[NSMutableArray alloc] init];
            for (NSString *key in [[mutableParameters allKeys] sortedArrayUsingSelector:@selector(compare:)]) {
                id value = [mutableParameters objectForKey:key];
                [canonicalStringArray addObject:[NSString stringWithFormat:@"%@=%@", key, value]];
            }
            NSString *canonicalString = [canonicalStringArray componentsJoinedByString:@"&"];
            canonicalString = CFBridgingRelease(CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault,
                                                                                        (__bridge CFStringRef)canonicalString,
                                                                                        NULL,
                                                                                        CFSTR(":,"),
                                                                                        kCFStringEncodingUTF8));
    
            NSString *method = [request HTTPMethod];
    
            NSString *signature = [NSString stringWithFormat:@"%@\n%@\n%@\n%@",method,self.region,self.formatPath,canonicalString];
    
            NSData *encodedSignatureData = RWMHMACSHA256EncodedDataFromStringWithKey(signature,self.secret);
            NSString *encodedSignatureString = RWMBase64EncodedStringFromData(encodedSignatureData);
    
            encodedSignatureString = CFBridgingRelease(CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault,
                                                                                               (__bridge CFStringRef)encodedSignatureString,
                                                                                               NULL,
                                                                                               CFSTR("+="),
                                                                                               kCFStringEncodingUTF8));
    
            canonicalString = [canonicalString stringByAppendingFormat:@"&%@=%@",RWMAmazonProductAdvertisingSignatureKey,encodedSignatureString];
    
            mutableRequest.URL = [NSURL URLWithString:[[mutableRequest.URL absoluteString] stringByAppendingFormat:mutableRequest.URL.query ? @"&%@" : @"?%@", canonicalString]];
    
        } else {
            if (error) {
                NSDictionary *userInfo = @{NSLocalizedDescriptionKey: NSLocalizedStringFromTable(@"Access Key and Secret Required", @"RWMAmazonProductAdvertisingManager", nil)};
                *error = [[NSError alloc] initWithDomain:RWMAmazonProductAdvertisingManagerErrorDomain code:NSURLErrorUserAuthenticationRequired userInfo:userInfo];
            }
        }
    
        return mutableRequest;
    
    }
    

    【讨论】:

    • 我相信这个答案会更好,如果它包含它的工作原理。毕竟,仅链接的答案容易受到链接腐烂的影响。
    【解决方案4】:

    感谢此页面上的所有答案。这对我有用(Swift 3.0):

    Pod 文件:

    pod 'AWSAPIGateway', '~> 2.4.7'
    

    Swift 代码

    static let kAmazonAccessID = "BLAH BLAH BLAH"
    static let kAmazonAccessSecretKey = "BLAH BLAH BLAH"
    
    static let kAmazonAssociateTag = "BLAH BLAH BLAH"
    private let timestampFormatter: DateFormatter
    
    init() {
        timestampFormatter = DateFormatter()
        timestampFormatter.timeZone = TimeZone(identifier: "GMT")
        timestampFormatter.dateFormat = "YYYY-MM-dd'T'HH:mm:ss'Z'"
        timestampFormatter.locale = Locale(identifier: "en_US_POSIX")
    }
    
    private func signedParametersForParameters(parameters: [String: String]) -> [String: String] {
        let sortedKeys = Array(parameters.keys).sorted(by: <)
    
        let query = sortedKeys.map { String(format: "%@=%@", $0, parameters[$0] ?? "") }.joined(separator: "&")
    
        let stringToSign = "GET\nwebservices.amazon.com\n/onca/xml\n\(query)"
    
        let dataToSign = stringToSign.data(using: String.Encoding.utf8)
        let signature = AWSSignatureSignerUtility.hmacSign(dataToSign, withKey: AmazonAPI.kAmazonAccessSecretKey, usingAlgorithm: UInt32(kCCHmacAlgSHA256))!
    
        var signedParams = parameters;
        signedParams["Signature"] = urlEncode(signature)
    
        return signedParams
    }
    
    public func urlEncode(_ input: String) -> String {
        let allowedCharacterSet = (CharacterSet(charactersIn: "!*'();:@&=+$,/?%#[] ").inverted)
    
        if let escapedString = input.addingPercentEncoding(withAllowedCharacters: allowedCharacterSet) {
            return escapedString
        }
    
        return ""
    }
    
    func send(url: String) -> String {
        guard let url = URL(string: url) else {
            print("Error! Invalid URL!") //Do something else
            return ""
        }
    
        let request = URLRequest(url: url)
        let semaphore = DispatchSemaphore(value: 0)
    
        var data: Data? = nil
    
        URLSession.shared.dataTask(with: request) { (responseData, _, _) -> Void in
            data = responseData
            semaphore.signal()
        }.resume()
    
        semaphore.wait(timeout: .distantFuture)
    
        let reply = data.flatMap { String(data: $0, encoding: .utf8) } ?? ""
        return reply
    }
    

    这是向亚马逊询问产品价格的功能:

    public func getProductPrice(_ asin: AmazonStandardIdNumber) -> Double {
    
        let operationParams: [String: String] = [
            "Service": "AWSECommerceService",
            "Operation": "ItemLookup",
            "ResponseGroup": "Offers",
            "IdType": "ASIN",
            "ItemId": asin,
            "AWSAccessKeyId": urlEncode(AmazonAPI.kAmazonAccessID),
            "AssociateTag": urlEncode(AmazonAPI.kAmazonAssociateTag),
            "Timestamp": urlEncode(timestampFormatter.string(from: Date())),]
    
        let signedParams = signedParametersForParameters(parameters: operationParams)
    
        let query = signedParams.map { "\($0)=\($1)" }.joined(separator: "&")
        let url = "http://webservices.amazon.com/onca/xml?" + query
    
        let reply = send(url: url)
    
        // USE THE RESPONSE
    }
    

    【讨论】:

    • 你节省了我很多时间
    • 嗨,我正在为 ItemSearch 使用“关键字”参数。但它说签名不匹配。
    【解决方案5】:

    P-double 帖子缺少几个步骤。

    在构造无符号字符串之前,您需要获取正确的时间戳值。

    NSTimeZone *zone = [NSTimeZone defaultTimeZone];                //get the current application default time zone
    NSInteger interval = [zone secondsFromGMTForDate:[NSDate date]];//sec Returns the time difference of the current application with the world standard time (Green Venice time)
    
    NSDate *nowDate = [NSDate dateWithTimeIntervalSinceNow:interval];
    NSDateFormatter * formatter = [[NSDateFormatter alloc] init];
    
    [formatter setTimeZone:[NSTimeZone systemTimeZone]];// get current date/time
    NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
    [dateFormatter setTimeZone:[NSTimeZone systemTimeZone]];
    
    // display in 12HR/24HR (i.e. 11:25PM or 23:25) format according to User Settings
    [dateFormatter setDateFormat:@"yyyy'-'MM'-'dd'T'HH':'mm':'ss'Z'"];
    NSString *currentTime = [dateFormatter stringFromDate:nowDate];
    
    NSString* encodedTime = (NSString *)CFBridgingRelease(CFURLCreateStringByAddingPercentEscapes(NULL, (__bridge CFStringRef) currentTime,NULL, CFSTR("!*'();:@&=+$,/?%#[]"),kCFStringEncodingUTF8));
    
    NSString* unsignedString = [NSString stringWithFormat:@"GET\nwebservices.amazon.com\n/onca/xml\nAWSAccessKeyId=AKIAI443QEMWI6KW55QQ&AssociateTag=sajjmanz-20&Condition=All&IdType=ASIN&ItemId=3492264077&Operation=ItemLookup&ResponseGroup=Images%%2CItemAttributes%%2COffers&Service=AWSECommerceService&Timestamp=%@&Version=2011-08-01", encodedTime];
    

    一旦日期是 url 友好的编码,其余的步骤就像一个魅力。

    最后,我还使用上面列出的 CFURLCreateStringByAddingPercentEscapes 对 AmazonAuthUtils HMACSign 消息调用生成的字符串进行编码。

    【讨论】:

      【解决方案6】:

      如果没有亚马逊的明确书面同意,在 iOS 应用程序中使用亚马逊的产品广告 API 是不“合法的”(根据亚马逊自己的指南),我错了吗?

      【讨论】:

      • 也许他在加利福尼亚,那里的点击包装协议的某些条款已经失效。
      • 我希望能够使用这个 api。你认为它会在总体上对 iOS 开放吗?
      • 对此了解不多。我可以肯定地告诉你一件事——许多开发人员永远不会考虑阅读这些指南并在他们的应用程序中使用它,除非有其他技术障碍或应用程序商店拒绝它们。
      • 我只是认为亚马逊会从允许在 iOS 应用程序中使用 API 以及允许联盟计划中受益匪浅(这当然也会使开发人员受益。)我希望'禁令很快解除。
      【解决方案7】:

      Swift 2.0

      这是一个为 Swift 签名一组参数的函数。请注意,此代码需要安装 AlamofireAWSCore Cocoapods。您还需要将 #import &lt;CommonCrypto/CommonCrypto.h&gt; 添加到您的 Objective-C 桥接头标头,否则将找不到 kCCHmacAlgSHA256

      private func signedParametersForParameters(parameters: [String: String]) -> [String: String] {
          let sortedKeys = Array(parameters.keys).sort(<)
      
          var components: [(String, String)] = []
          for key in sortedKeys {
              components += ParameterEncoding.URLEncodedInURL.queryComponents(key, parameters[key]!)
          }
      
          let query = (components.map { "\($0)=\($1)" } as [String]).joinWithSeparator("&")
      
          let stringToSign = "GET\nwebservices.amazon.com\n/onca/xml\n\(query)"
          let dataToSign = stringToSign.dataUsingEncoding(NSUTF8StringEncoding)
          let signature = AWSSignatureSignerUtility.HMACSign(dataToSign, withKey: kAmazonAccessSecretKey, usingAlgorithm: UInt32(kCCHmacAlgSHA256))!
      
          let signedParams = parameters + ["Signature": signature]
      
          return signedParams
      }
      

      它是这样称呼的:

      let operationParams: [String: String] = ["Service": "AWSECommerceService", "Operation": "ItemLookup", "ItemId": "045242127733", "IdType": "UPC", "ResponseGroup": "Images,ItemAttributes", "SearchIndex": "All"]
      let keyParams = ["AWSAccessKeyId": kAmazonAccessID, "AssociateTag": kAmazonAssociateTag, "Timestamp": timestampFormatter.stringFromDate(NSDate())]
      let fullParams = operationParams + keyParams
      
      let signedParams = signedParametersForParameters(fullParams)
      
      Alamofire.request(.GET, "http://webservices.amazon.com/onca/xml", parameters: signedParams).responseString { (response) in
          print("Success: \(response.result.isSuccess)")
          print("Response String: \(response.result.value)")
      }
      

      最后,timestampFormatter 是这样声明的:

      private let timestampFormatter: NSDateFormatter
      
      init() {
          timestampFormatter = NSDateFormatter()
          timestampFormatter.dateFormat = AWSDateISO8601DateFormat3
          timestampFormatter.timeZone = NSTimeZone(name: "GMT")
          timestampFormatter.locale = NSLocale(localeIdentifier: "en_US_POSIX")
      }
      

      您可以使用/修改以满足您的需要,但所有必要的东西都应该在那里。

      【讨论】:

      • 这似乎是我所见过的最好的答案,但它仍然给我一个错误:...SignatureDoesNotMatch请求签名我们计算的与您提供的签名不符。检查您的 AWS 秘密访问密钥和签名方法......我几天来一直试图解决这个问题,任何帮助将不胜感激。 :)
      • 太棒了。您是否有机会在 Objective-C 中获得此代码?看起来很棒。
      • 恐怕我没有它用于 ObjC,主要是因为它使用的 Alamofire 东西我认为只有 Swift。也许一些最重要的答案可以帮助 ObjC?自从我上次看这个已经有一段时间了。
      • @CoryImdieke In "ResponseGroup": "Images,ItemAttributes" 如果它的两个值像图像,属性那么它说我们计算的请求签名与您提供的签名不匹配。检查您的 AWS 秘密访问密钥和签名方法,或者它的响应组值是否类似于“图像”,那么我可以获取数据......问题是什么。我做不到。
      【解决方案8】:

      除了使用现代 OS X 中已弃用的 CommonCrypto,您还可以使用 SecTransforms:

      CFErrorRef error = NULL;
      
      SecTransformRef digestRef = SecDigestTransformCreate(kSecDigestHMACSHA2, 256, &error);
      SecTransformSetAttribute(digestRef, kSecTransformInputAttributeName, (__bridge CFDataRef)self, &error);
      SecTransformSetAttribute(digestRef, kSecDigestHMACKeyAttribute, (__bridge CFDataRef)key, &error);
      
      CFDataRef resultData = SecTransformExecute(digestRef, &error);
      NSData* hashData = (__bridge NSData*)resultData;
      
      CFRelease(digestRef);
      

      【讨论】:

      • 我不相信这将适用于 iOS,其中 SecDigestTransformCreate() 不存在(从 iOS 8.0 开始)。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-05-15
      • 1970-01-01
      • 1970-01-01
      • 2020-10-29
      相关资源
      最近更新 更多