Amazon Product Advertising API Signature in iOS

2019-01-22 00:14发布

I am trying to access Amazon's Product Advertising API in my iOS application. Creating the signature seems to be the tough part. On this page:

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

It says to "Calculate an RFC 2104-compliant HMAC with the SHA256 hash algorithm". Amazon also provides a java class to do this for you:

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

Does anybody know how I can do this in Objective-C instead? I looked into the AWS iOS SDK, but it doesn't seem to include the Product Advertising API.

8条回答
做自己的国王
2楼-- · 2019-01-22 00:44

Just to add a bit to camelcc's excellent observation. This does indeed work well for signing requests to the Amazon Product Advertising API. I had to mess around a bit to get it working.

Get the SDK installed and #import <AWSiOSSDK/AmazonAuthUtils.h>

First you've got to organize the request string into the correct order, as per the Amazon docs. I found this page very useful in explaining how to order the request

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

Note the need for new-line characters in the string, my unsigned string looked like this

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

No spaces anywhere, but \n characters in the right places. The convert this to NSData like so

NSData *dataToSign = [unsignedString dataUsingEncoding:NSUTF8StringEncoding];

Then call

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

This returns your signature as an NSString. You'll need to URL encode this (ie swapping illegal/unsafe charecters for %0x symbols (ie '=' converts to '%3D'))

Once this is done, stick it in your request and hopefully you are good to go!

查看更多
倾城 Initia
3楼-- · 2019-01-22 00:49

Thanks for all answers on this page. Here is what has worked for me (Swift 3.0):

Podfile:

pod 'AWSAPIGateway', '~> 2.4.7'

Swift code

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
}

and here is the function that ask Amazon for price of a product:

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
}
查看更多
登录 后发表回答