You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.11 09:13:19