通过 Obj-c 查询 REST API 时出现 Azure DocumentDB 间歇性 401 错误

Posted

技术标签:

【中文标题】通过 Obj-c 查询 REST API 时出现 Azure DocumentDB 间歇性 401 错误【英文标题】:Azure DocumentDB Intermittent 401 error when querying REST API via Obj-c 【发布时间】:2016-08-30 05:42:17 【问题描述】:

我负责使用 REST API 方案实现 Azure DocumentDB 系统的基于 Objective-c 的 ios 查询。利用在 github 上找到的代码,特别是 https://github.com/Azure/azure-storage-ios,我能够生成一个请求,该请求可以适当地进行身份验证并返回适当的数据......有时。

问题:我间歇性地收到来自服务器的 401(身份验证失败)错误响应。通过 Node.js 发出相同的请求不会遇到这种行为,所以我认为这是我的 Objective-c 实现的问题。

- (NSMutableURLRequest *) RequestWithQuery:(NSString*)query Parameters:(NSArray*)parameters 

NSError* error;
NSDictionary* dictionaryOfBodyContents = @@"query":query,
                                           @"parameters":parameters;
NSData* body = [NSJSONSerialization dataWithJSONObject:dictionaryOfBodyContents
                                               options:NSJSONWritingPrettyPrinted
                                                 error:&error];

if(error != nil) 
    NSLog(@"AzureRequestWithQueryParameters error generating the body: %@",error);
    return nil;


char buffer[30];
struct tm * timeptr;

time_t time = (time_t) [[NSDate date] timeIntervalSince1970];
timeptr = gmtime(&time);
if (!strftime_l(buffer, 30, [@"%a, %d %b %Y %T GMT" UTF8String], timeptr, NULL))

    NSException* myException = [NSException
                                exceptionWithName:@"Error in date/time format"
                                reason:@"Unknown"
                                userInfo:nil];
    @throw myException;

NSString* date = [NSString stringWithUTF8String:buffer];
// generate auth token
NSString* authorizationToken = [self AuthorizationTokenForTableQueryWithDate:date];

// generate header contents
NSDictionary* dictionaryOfHeaderContents = @@"authorization":authorizationToken,
                                             @"connection":AZURE_CONNECTION_HEADER_CONNECTION,
                                             @"content-type":AZURE_CONNECTION_HEADER_CONTENTTYPE,
                                             @"content-length":[NSString stringWithFormat:@"%lu",(unsigned long)[body length]],
                                             @"x-ms-version":AZURE_CONNECTION_APIVERSION,
                                             @"x-ms-documentdb-isquery":@"true",
                                             @"x-ms-date":date.lowercaseString,
                                             @"cache-control":@"no-cache",
                                             @"user-agent":AZURE_CONNECTION_HEADER_USERAGENT,
                                             @"accept":@"application/json";

// generate url contents
NSString* urlString = [NSString stringWithFormat:@"https://%@:%@/%@", AZURE_URL_HOST, AZURE_URL_PORT, AZURE_URL_DOCUMENTS];
NSURL* url = [NSURL URLWithString:urlString];

NSMutableURLRequest* request = [[NSMutableURLRequest alloc] initWithURL:url];
[request setHTTPMethod:AZURE_CONNECTION_METHOD];
[request setAllHTTPHeaderFields:dictionaryOfHeaderContents];
[request setHTTPBody:body];
return request;


- (NSString*) AuthorizationTokenForTableQueryWithDate:(NSString*)date 
//
//  Based on https://msdn.microsoft.com/en-us/library/azure/dd179428.aspx under "Table Service (Shared Key Authentication)"
//
//    generating a authentication token is a Hash-based Message Authentication Code (HMAC) constructed from the request
//      and computed by using the SHA256 algorithm, and then encoded by using Base64 encoding.
//
//    StringToSign =  VERB + "\n" +
//                    Content-MD5 + "\n" +
//                    Content-Type + "\n" +
//                    Date + "\n" +
//                    CanonicalizedHeaders +
//                    CanonicalizedResource;
//
NSString* StringToSign = [NSString stringWithFormat:@"%@\n%@\n%@\n%@\n\n",
                          AZURE_CONNECTION_METHOD.lowercaseString?:@"",
                          AZURE_RESOURCE_TYPE.lowercaseString?:@"",
                          AZURE_URL_COLLECTIONS.lowercaseString?:@"",
                          date.lowercaseString?:@""];

// Generate Key/Message pair
NSData* keyData = [[NSData alloc] initWithBase64EncodedString:AZURE_AUTH_KEY options:NSDataBase64DecodingIgnoreUnknownCharacters];
NSData* messageData = [StringToSign dataUsingEncoding:NSUTF8StringEncoding];

// Encrypt your Key/Message using HMAC SHA256
NSMutableData* HMACData = [NSMutableData dataWithLength:CC_SHA256_DIGEST_LENGTH];
CCHmac(kCCHmacAlgSHA256, keyData.bytes, keyData.length, messageData.bytes, messageData.length, HMACData.mutableBytes);

// Take your encrypted data, and generate a token that Azure likes.
NSString* signature = [HMACData base64EncodedStringWithOptions:NSDataBase64Encoding64CharacterLineLength];
NSString* unencodedToken = [NSString stringWithFormat:@"type=master&ver=1.0&sig=%@",signature];
NSString* authorizationToken = [unencodedToken stringByReplacingOccurrencesOfString:@"&" withString:@"%26"];
authorizationToken = [authorizationToken stringByReplacingOccurrencesOfString:@"=" withString:@"%3D"];

return authorizationToken;

如果有人遇到过类似的间歇性 401 并能够解决任何帮助,我们将不胜感激。或者考虑到上述代码的调试步骤的建议,我尝试将时间戳减少几秒钟,类似的间歇性故障。

虽然简单地在失败时重试几次,同时减少秒数会在 1-2 次重试中产生 200 响应,但我不认为这是一个理想的解决方案。

感谢您的宝贵时间。

更新:请参阅下面 Andrew Liu 的解释,了解此失败的原因。我已将他的回复标记为答案,下面是更新后的 sn-p 代码。

NSString* unencodedToken = [NSString stringWithFormat:@"type=master&ver=1.0&sig=%@",signature];
//    NSString* authorizationToken = [unencodedToken stringByReplacingOccurrencesOfString:@"&" withString:@"%26"];
//    authorizationToken = [authorizationToken stringByReplacingOccurrencesOfString:@"=" withString:@"%3D"];
NSString* authorizationToken = [unencodedToken stringByAddingPercentEncodingWithAllowedCharacters:[[NSCharacterSet characterSetWithCharactersInString:@"&+="] invertedSet]];
return authorizationToken;

【问题讨论】:

【参考方案1】:

401(身份验证失败)通常表示身份验证令牌有问题。

请务必注意,身份验证标记是 Base64 编码的字符串 - 这意味着它可以包含 + 字符。

db 服务器期望 auth 令牌中的 + 字符被 url 编码 (%2B)...一些但不是所有 HTTP 客户端会自动为您编码 HTTP 标头。

我怀疑以下变量的 url 编码或将 + 转换为 %2B 将解决您的间歇性 401 问题:

NSString* signature = [HMACData base64EncodedStringWithOptions:NSDataBase64Encoding64CharacterLineLength];

【讨论】:

这是一个很好的捕获,正是问题所在,所有失败的身份验证令牌都包含一个我没有正确转换的 +。我已经接受了答案,谢谢安德鲁!【参考方案2】:

我以前见过这个问题,通常它与协议的最后两个步骤有关,即 base64 编码是古怪的,或者是 URI 编码。调试此问题的一种方法是打印您发送的身份验证令牌,以防失败,并查看是否有任何可能未正确传输的奇怪字符。您可以在此处发布有问题的令牌,我可以看看。

【讨论】:

以上是关于通过 Obj-c 查询 REST API 时出现 Azure DocumentDB 间歇性 401 错误的主要内容,如果未能解决你的问题,请参考以下文章

api rest 调用更新 django 模型时出现错误 415

从sharepoint rest api下载文件时出现400错误

在 iOS 中与 REST Api 进行会话时出现 QuickBlox“意外签名”错误

从 Ajax 调用 Spring Boot Rest API 时出现 CORS 错误

使用基本授权(用户名和密码)时出现 VSTS REST API 错误

尝试创建 Quickblox 群聊对话框,使用 REST API 登录用户时出现错误。