我正在尝试在iOS应用程序中访问亚马逊的产品广告API。创建签名似乎是一个艰难的部分。在此页面上:
http://docs.amazonwebservices.com/AWSECommerceService/latest/DG/rest-signature.html
它说“使用SHA256哈希算法计算符合RFC 2104的HMAC”。亚马逊还提供了一个java类来为您执行此操作:
http://docs.amazonwebservices.com/AWSECommerceService/latest/DG/AuthJavaSampleSig2.html
有人知道如何在Objective-C中做到这一点吗?我查看了AWS iOS SDK,但它似乎没有包含产品广告API。
答案 0 :(得分:11)
实际上,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
答案 1 :(得分:11)
只是为camelcc的优秀观察添加一点。这确实适用于签署对Amazon Product Advertising 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'))
完成后,请将其粘贴在您的请求中,希望您好好去!
答案 2 :(得分:8)
查看我的亚马逊产品广告客户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;
}
答案 3 :(得分:5)
感谢此页面上的所有答案。这对我有用(Swift 3.0):
Podfile:
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
}
答案 4 :(得分:4)
P-double帖子缺少几个步骤。
在构造无符号字符串之前,您需要获取Timestamp值。
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消息调用生成的字符串。
答案 5 :(得分:4)
如果没有亚马逊的明确书面同意,在iOS应用中使用亚马逊的产品广告API并不是“合法的”(根据亚马逊自己的指导原则),我是错的吗?
答案 6 :(得分:4)
Swift 2.0
这是一个为Swift签署一组参数的函数。请注意,此代码需要安装Alamofire
和AWSCore
Cocoapods。您还需要在Objective-C Bridging标头中添加#import <CommonCrypto/CommonCrypto.h>
,否则将无法找到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")
}
您可以使用/修改以满足您的需求,但必要的一切都应该在那里。
答案 7 :(得分:3)
您可以使用SecTransforms代替使用在现代OS X中弃用的CommonCrypto:
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);