iOS端生成匹配Yahoo OAuth API的HMAC-SHA1签名遇401错误求助
排查Yahoo OAuth 1.0签名无效(401错误)的关键点
我帮你排查了几个可能导致签名无效的核心问题,这些都是OAuth 1.0签名实现中容易踩的坑:
1. 签名基字符串拼接错误
你的代码里签名基字符串多了一个多余的&:
NSString *signature = [NSString stringWithFormat:@"GET&%@&%@&", encodedApiURL, encodedParameters];
OAuth 1.0的签名基字符串格式是HTTP_METHOD&ENCODED_URL&ENCODED_PARAMETERS,三个部分用&分隔,不需要结尾额外的&。修正后应该是:
NSString *signatureBaseString = [NSString stringWithFormat:@"GET&%@&%@", encodedApiURL, encodedParameters];
2. 参数未按字典序排序
OAuth要求所有请求参数(包括lat、lon、format这类非OAuth参数)必须按字典序升序排列,你的参数拼接顺序是先OAuth参数再业务参数,不符合规范。正确的做法是把所有参数放到字典里排序后再拼接:
NSDictionary *allParams = @{ @"oauth_consumer_key": oauth_consumer_key, @"oauth_nonce": oauth_nonce, @"oauth_signature_method": oauth_signature_method, @"oauth_timestamp": oauth_timestamp, @"oauth_version": oauth_version, @"lat": @"30.707640", @"lon": @"76.703553", @"format": @"json" }; // 按key升序排序 NSArray *sortedKeys = [[allParams allKeys] sortedArrayUsingSelector:@selector(compare:)]; NSMutableArray *paramPairs = [NSMutableArray array]; for (NSString *key in sortedKeys) { NSString *encodedKey = urlformdata_encode(key); NSString *encodedValue = urlformdata_encode(allParams[key]); [paramPairs addObject:[NSString stringWithFormat:@"%@=%@", encodedKey, encodedValue]]; } NSString *parameters = [paramPairs componentsJoinedByString:@"&"]; NSString *encodedParameters = urlformdata_encode(parameters);
3. HMAC密钥格式错误
OAuth 1.0中,当使用客户端凭证流程(没有access token)时,HMAC的密钥应该是编码后的消费者密钥 + &(注意即使没有token,也要保留这个&)。你直接使用了原始的consumerSecret,没有编码也没加&,这会导致签名完全错误:
// 先编码消费者密钥,再拼接& NSString *encodedConsumerSecret = urlformdata_encode(consumerSecret); NSString *hmacKey = [NSString stringWithFormat:@"%@&", encodedConsumerSecret]; NSString *signature = [self hmacsha1:signatureBaseString secret:hmacKey];
4. Nonce不能硬编码
你的oauth_nonce是固定值@"840eee23-f521-4d52-bca9-3a715894f22",OAuth要求每次请求必须生成唯一的nonce(比如用UUID生成),否则Yahoo会认为是重复请求并拒绝:
NSString *oauth_nonce = [[NSUUID UUID] UUIDString];
5. 确认URL编码的正确性
你的urlformdata_encode函数必须严格遵循RFC 3986标准:
- 空格要编码为
%20(不是+) - 所有非字母数字的字符都要编码,包括
&、=、@等
如果你的自定义编码函数不符合要求,建议用系统的stringByAddingPercentEncodingWithAllowedCharacters:并指定正确的字符集:
NSString *urlformdata_encode(NSString *string) { NSCharacterSet *allowedChars = [NSCharacterSet characterSetWithCharactersInString:@"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~"]; return [string stringByAddingPercentEncodingWithAllowedCharacters:allowedChars]; }
修正后的核心代码片段
把这些点整合后,你的generateOAuthHeader方法应该类似这样:
- (NSString *)generateOAuthHeader { NSString *apiURL = @"https://weather-ydn-yql.media.yahoo.com/forecastrss"; NSString *oauth_consumer_key = @"dj0yJmk9V004dENIbkd6dXh3JnM9Y29uc3VtZXJzZWNyZXQmc3Y9MCZ4PTg0"; NSString *consumerSecret = @"9b54fad8d2bccedaa17eddfe342a0178ee72eb34"; NSString *oauth_nonce = [[NSUUID UUID] UUIDString]; NSString *oauth_signature_method = @"HMAC-SHA1"; NSString *oauth_timestamp = [NSString stringWithFormat:@"%.0f", [[NSDate date] timeIntervalSince1970]]; NSString *oauth_version = @"1.0"; // 1. 整理所有参数并按字典序排序 NSDictionary *allParams = @{ @"oauth_consumer_key": oauth_consumer_key, @"oauth_nonce": oauth_nonce, @"oauth_signature_method": oauth_signature_method, @"oauth_timestamp": oauth_timestamp, @"oauth_version": oauth_version, @"lat": @"30.707640", @"lon": @"76.703553", @"format": @"json" }; NSArray *sortedKeys = [[allParams allKeys] sortedArrayUsingSelector:@selector(compare:)]; NSMutableArray *paramPairs = [NSMutableArray array]; for (NSString *key in sortedKeys) { NSString *encodedKey = [self urlformdata_encode:key]; NSString *encodedValue = [self urlformdata_encode:allParams[key]]; [paramPairs addObject:[NSString stringWithFormat:@"%@=%@", encodedKey, encodedValue]]; } NSString *parameters = [paramPairs componentsJoinedByString:@"&"]; // 2. 生成签名基字符串 NSString *encodedApiURL = [self urlformdata_encode:apiURL]; NSString *encodedParameters = [self urlformdata_encode:parameters]; NSString *signatureBaseString = [NSString stringWithFormat:@"GET&%@&%@", encodedApiURL, encodedParameters]; // 3. 生成HMAC密钥并计算签名 NSString *encodedConsumerSecret = [self urlformdata_encode:consumerSecret]; NSString *hmacKey = [NSString stringWithFormat:@"%@&", encodedConsumerSecret]; NSString *signature = [self hmacsha1:signatureBaseString secret:hmacKey]; // 签名需要再次编码 signature = [self urlformdata_encode:signature]; // 4. 构建Authorization头 NSString *authorizationHeader = [NSString stringWithFormat:@"OAuth oauth_consumer_key=\"%@\", oauth_nonce=\"%@\", oauth_signature_method=\"%@\", oauth_timestamp=\"%@\", oauth_version=\"%@\", oauth_signature=\"%@\"", oauth_consumer_key, oauth_nonce, oauth_signature_method, oauth_timestamp, oauth_version, signature]; return authorizationHeader; } // 符合RFC3986的URL编码方法 - (NSString *)urlformdata_encode:(NSString *)string { NSCharacterSet *allowedChars = [NSCharacterSet characterSetWithCharactersInString:@"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~"]; return [string stringByAddingPercentEncodingWithAllowedCharacters:allowedChars]; }
另外,还要确保你的hmacsha1方法正确实现了HMAC-SHA1并返回Base64编码的字符串,这是OAuth要求的签名格式。
内容的提问来源于stack exchange,提问作者devdoe
相关产品推荐
相关产品推荐

