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

我负责使用REST APIscheme实现基于Objective-C的Azure DocumentDB系统的iOS查询。 利用在github上find的代码,特别是https://github.com/Azure/azure-storage-ios我能够生成一个适当的validation和返回适当的数据….有时。

问题:我间歇地收到来自服务器的401(authentication失败)错误响应。 通过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,并能够解决任何帮助,将不胜感激。 或者考虑到上述代码的debugging步骤,我曾尝试将时间戳递减几秒钟,类似的间歇性故障。

虽然简单地重试几次失败,而递减秒数导致200次响应1-2次重试,我不觉得这是一个理想的解决scheme。

感谢您的时间。

更新:请参阅下面的安德鲁刘的解释为什么这个失败的原因。 我已经标记了他的回答作为答案,下面是更新的代码片段。

 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; 

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

请注意,auth令牌是一个Base64编码的string,这意味着它可以包含+字符。

数据库服务器期望身份validation令牌中的+字符被url编码( %2B )…有些但不是所有的HTTP客户端都会自动为您编码HTTP头。

我怀疑以下variables的URL编码或转换+ %2B以解决您的间歇性401问题:

 NSString* signature = [HMACData base64EncodedStringWithOptions:NSDataBase64Encoding64CharacterLineLength]; 

我以前见过这个问题,通常它与协议的最后两个步骤有关,即base64编码是whacky,或者是URI编码。 debugging这种方法的一种方法是打印您发送的身份validation令牌,以防万一发生故障,并查看是否有可能无法正确传输的奇怪字符。 你可以在这里发布马车令牌,我可以看看。

Interesting Posts