【问题标题】:NSURLConnection and Basic HTTP Authentication in iOSiOS 中的 NSURLConnection 和基本 HTTP 身份验证
【发布时间】:2010-12-30 16:59:25
【问题描述】:

我需要使用基本Authentication 调用初始GET HTTP request。这将是第一次将请求发送到服务器,并且我已经拥有 username & password,因此无需来自服务器的授权挑战。

第一个问题:

  1. NSURLConnection 是否必须设置为同步才能进行基本身份验证?根据这个post的回答,如果你选择异步路由,似乎你不能做Basic Auth。

  2. 任何人都知道任何一些示例代码来说明GET request 上的基本身份验证,而无需质询响应? Apple's documentation 显示了一个示例,但仅在服务器向客户端发出质询请求之后。

我对 SDK 的网络部分有点陌生,我不确定我应该使用哪些其他类来使其正常工作。 (我看到了NSURLCredential 类,但它似乎只在客户端向服务器请求授权资源后与NSURLAuthenticationChallenge 一起使用)。

【问题讨论】:

    标签: ios iphone objective-c nsurlconnection


    【解决方案1】:

    我正在使用与MGTwitterEngine 的异步连接,它在NSMutableURLRequest (theRequest) 中设置授权,如下所示:

    NSString *authStr = [NSString stringWithFormat:@"%@:%@", [self username], [self password]];
    NSData *authData = [authStr dataUsingEncoding:NSASCIIStringEncoding];
    NSString *authValue = [NSString stringWithFormat:@"Basic %@", [authData base64EncodingWithLineLength:80]];
    [theRequest setValue:authValue forHTTPHeaderField:@"Authorization"];
    

    我不认为这种方法需要通过挑战循环,但我可能错了

    【讨论】:

    • 那部分不是我写的,它只是 MGTwitterEngine 的一部分,来自一个添加到 NSData 的类别。在此处查看 NSData+Base64.h/m:github.com/ctshryock/MGTwitterEngine
    • 对于 base64 编码 ([authData base64EncodedString]) 将来自 Matt Gallagher 的 NSData+Base64.h 和 .m 文件添加到您的 XCode-Project (Base64 encoding options on the Mac and iPhone)。
    • NSASCIIStringEncoding 将损坏非 usascii 用户名或密码。改用 NSUTF8StringEncoding
    • base64EncodingWithLineLength 在 2014 年不存在于 NSData。请改用 base64Encoding。
    • @bickster base64Encoding自 iOS 7.0 和 OS X 10.9 起已弃用。我改用[authData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed]。还可以使用 `NSDataBase64Encoding64CharacterLineLength` 或 NSDataBase64Encoding76CharacterLineLength
    【解决方案2】:

    即使问题得到了回答,我也想提出不需要外部库的解决方案,我在另一个线程中找到了:

    // Setup NSURLConnection
    NSURL *URL = [NSURL URLWithString:url];
    NSURLRequest *request = [NSURLRequest requestWithURL:URL
                                             cachePolicy:NSURLRequestUseProtocolCachePolicy
                                         timeoutInterval:30.0];
    
    NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
    [connection start];
    [connection release];
    
    // NSURLConnection Delegates
    - (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {
        if ([challenge previousFailureCount] == 0) {
            NSLog(@"received authentication challenge");
            NSURLCredential *newCredential = [NSURLCredential credentialWithUser:@"USER"
                                                                        password:@"PASSWORD"
                                                                     persistence:NSURLCredentialPersistenceForSession];
            NSLog(@"credential created");
            [[challenge sender] useCredential:newCredential forAuthenticationChallenge:challenge];
            NSLog(@"responded to authentication challenge");    
        }
        else {
            NSLog(@"previous authentication failure");
        }
    }
    
    - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
        ...
    }
    
    - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
        ...
    }
    
    - (void)connectionDidFinishLoading:(NSURLConnection *)connection {
        ...
    }
    
    - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
        ...
    }
    

    【讨论】:

    • 这与其他解决方案不太一样:它首先联系服务器,接收 401 响应,然后使用正确的凭据进行响应。所以你在浪费一次往返。从好的方面来说,您的代码将处理其他挑战,例如 HTTP Digest Auth。这是一种权衡。
    • 无论如何,这是做这件事的“正确方法”。所有其他方式都是捷径。
    • 非常感谢! @moosgummi
    • @dom 我已经使用了它,但由于某种原因没有调用 didRecieveAuthenticationChallenge 并且我从站点收到了 403 拒绝访问消息。有谁知道出了什么问题?
    • 是的,这是唯一正确的方法。而且它只会在第一次引起 401 响应。对同一服务器的后续请求将通过身份验证发送。
    【解决方案3】:

    这是一个不涉及第三者的详细答案:

    请看这里:

    //username and password value
    NSString *username = @“your_username”;
    NSString *password = @“your_password”;
    
    //HTTP Basic Authentication
    NSString *authenticationString = [NSString stringWithFormat:@"%@:%@", username, password]];
    NSData *authenticationData = [authenticationString dataUsingEncoding:NSASCIIStringEncoding];
    NSString *authenticationValue = [authenticationData base64Encoding];
    
    //Set up your request
    NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:@"http://www.your-api.com/“]];
    
    // Set your user login credentials
    [request setValue:[NSString stringWithFormat:@"Basic %@", authenticationValue] forHTTPHeaderField:@"Authorization"];
    
    // Send your request asynchronously
    [NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *responseCode, NSData *responseData, NSError *responseError) {
          if ([responseData length] > 0 && responseError == nil){
                //logic here
          }else if ([responseData length] == 0 && responseError == nil){
                 NSLog(@"data error: %@", responseError);
                 UIAlertView *alert = [[UIAlertView alloc] initWithTitle:nil message:@"Error accessing the data" delegate:nil cancelButtonTitle:@"Close" otherButtonTitles:nil];
                 [alert show];
                 [alert release];
          }else if (responseError != nil && responseError.code == NSURLErrorTimedOut){
                 NSLog(@"data timeout: %@”, NSURLErrorTimedOut);
                 UIAlertView *alert = [[UIAlertView alloc] initWithTitle:nil message:@"connection timeout" delegate:nil cancelButtonTitle:@"Close" otherButtonTitles:nil];
                 [alert show];
                 [alert release];
          }else if (responseError != nil){
                 NSLog(@"data download error: %@”,responseError);
                 UIAlertView *alert = [[UIAlertView alloc] initWithTitle:nil message:@"data download error" delegate:nil cancelButtonTitle:@"Close" otherButtonTitles:nil];
                 [alert show];
                 [alert release];
          }
    }]
    

    请让我知道您对此的反馈。

    谢谢

    【讨论】:

    • 您用于将 NSData 转换为 NSString 的 base64Encoding 方法现已弃用:- (NSString *)base64Encoding NS_DEPRECATED(10_6, 10_9, 4_0, 7_0); 最好改用 NSDataBase64Encoding 类别。
    【解决方案4】:

    如果您不想导入整个 MGTwitterEngine 并且不执行异步请求 然后你可以使用 http://www.chrisumbel.com/article/basic_authentication_iphone_cocoa_touch

    对用户名和密码进行base64编码 所以替换

    NSString *authValue = [NSString stringWithFormat:@"Basic %@", [authData base64EncodingWithLineLength:80]];
    

    NSString *encodedLoginData = [Base64 encode:[loginString dataUsingEncoding:NSUTF8StringEncoding]];
    

    之后

    您需要包含以下文件

    static char *alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
    
    @implementation Base64
    +(NSString *)encode:(NSData *)plainText {
        int encodedLength = (((([plainText length] % 3) + [plainText length]) / 3) * 4) + 1;
        unsigned char *outputBuffer = malloc(encodedLength);
        unsigned char *inputBuffer = (unsigned char *)[plainText bytes];
    
        NSInteger i;
        NSInteger j = 0;
        int remain;
    
        for(i = 0; i < [plainText length]; i += 3) {
            remain = [plainText length] - i;
    
            outputBuffer[j++] = alphabet[(inputBuffer[i] & 0xFC) >> 2];
            outputBuffer[j++] = alphabet[((inputBuffer[i] & 0x03) << 4) | 
                                         ((remain > 1) ? ((inputBuffer[i + 1] & 0xF0) >> 4): 0)];
    
            if(remain > 1)
                outputBuffer[j++] = alphabet[((inputBuffer[i + 1] & 0x0F) << 2)
                                             | ((remain > 2) ? ((inputBuffer[i + 2] & 0xC0) >> 6) : 0)];
            else 
                outputBuffer[j++] = '=';
    
            if(remain > 2)
                outputBuffer[j++] = alphabet[inputBuffer[i + 2] & 0x3F];
            else
                outputBuffer[j++] = '=';            
        }
    
        outputBuffer[j] = 0;
    
        NSString *result = [NSString stringWithCString:outputBuffer length:strlen(outputBuffer)];
        free(outputBuffer);
    
        return result;
    }
    @end
    

    【讨论】:

      【解决方案5】:

      由于不推荐使用 NSData::dataUsingEncoding (ios 7.0),您可以使用此解决方案:

      // Forming string with credentials 'myusername:mypassword'
      NSString *authStr = [NSString stringWithFormat:@"%@:%@", username, password];
      // Getting data from it
      NSData *authData = [authStr dataUsingEncoding:NSASCIIStringEncoding];
      // Encoding data with base64 and converting back to NSString
      NSString* authStrData = [[NSString alloc] initWithData:[authData base64EncodedDataWithOptions:NSDataBase64EncodingEndLineWithLineFeed] encoding:NSASCIIStringEncoding];
      // Forming Basic Authorization string Header
      NSString *authValue = [NSString stringWithFormat:@"Basic %@", authStrData];
      // Assigning it to request
      [request setValue:authValue forHTTPHeaderField:@"Authorization"];
      

      【讨论】:

        【解决方案6】:

        如果您使用GTMHTTPFetcher 进行连接,则基本身份验证也相当简单。您只需在开始提取之前向提取器提供凭据即可。

        NSString * urlString = @"http://www.testurl.com/";
        NSURL * url = [NSURL URLWithString:urlString];
        NSMutableURLRequest * request = [NSMutableURLRequest requestWithURL:url];
        
        NSURLCredential * credential = [NSURLCredential credentialWithUser:@"username" password:@"password" persistence:NSURLCredentialPersistenceForSession];
        
        GTMHTTPFetcher * gFetcher = [GTMHTTPFetcher fetcherWithRequest:request];
        gFetcher.credential = credential;
        
        [gFetcher beginFetchWithDelegate:self didFinishSelector:@selector(fetchCompleted:withData:andError:)];
        

        【讨论】:

          【解决方案7】:

          您能告诉我在您的示例代码中将编码行长度限制为 80 的原因是什么?我认为 HTTP 标头的最大长度约为 4k(或者某些服务器可能不会花费比这更长的时间)。 – Justin Galzic 2009 年 12 月 29 日 17:29

          不限于80,它是NSData+Base64.h/m中base64EncodingWithLineLength方法的一个选项,你可以将你的编码字符串分成多行,这对于其他应用程序很有用,例如nntp传输。我相信 twitter 引擎作者选择 80 的长度足以容纳大多数用户/密码编码结果到一行。

          【讨论】:

            【解决方案8】:

            您可以使用AFNetworking(它是开源的),这是对我有用的代码。此代码发送带有基本身份验证的文件。只需更改网址、电子邮件和密码。

            NSString *serverUrl = [NSString stringWithFormat:@"http://www.yoursite.com/uploadlink", profile.host];
            NSMutableURLRequest *request = [[AFHTTPRequestSerializer serializer] requestWithMethod:@"POST" URLString:serverUrl parameters:nil error:nil];
            
            
            NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
            AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:configuration];
            
            // Forming string with credentials 'myusername:mypassword'
            NSString *authStr = [NSString stringWithFormat:@"%@:%@", email, emailPassword];
            // Getting data from it
            NSData *authData = [authStr dataUsingEncoding:NSASCIIStringEncoding];
            // Encoding data with base64 and converting back to NSString
            NSString* authStrData = [[NSString alloc] initWithData:[authData base64EncodedDataWithOptions:NSDataBase64EncodingEndLineWithLineFeed] encoding:NSASCIIStringEncoding];
            // Forming Basic Authorization string Header
            NSString *authValue = [NSString stringWithFormat:@"Basic %@", authStrData];
            // Assigning it to request
            [request setValue:authValue forHTTPHeaderField:@"Authorization"];
            
            manager.responseSerializer = [AFHTTPResponseSerializer serializer];
            
            NSURL *filePath = [NSURL fileURLWithPath:[url path]];
            NSURLSessionUploadTask *uploadTask = [manager uploadTaskWithRequest:request fromFile:filePath progress:^(NSProgress * _Nonnull uploadProgress) {
            // This is not called back on the main queue.
            // You are responsible for dispatching to the main queue for UI updates
                 dispatch_async(dispatch_get_main_queue(), ^{
                            //Update the progress view
                            LLog(@"progres increase... %@ , fraction: %f", uploadProgress.debugDescription, uploadProgress.fractionCompleted);
                        });
                    } completionHandler:^(NSURLResponse *response, id responseObject, NSError *error) {
                        if (error) {
                            NSLog(@"Error: %@", error);
                        } else {
                            NSLog(@"Success: %@ %@", response, responseObject);
                        }
                    }];
            [uploadTask resume];
            

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2013-09-01
              • 2015-09-09
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2013-10-06
              • 2015-10-01
              • 2014-11-06
              相关资源
              最近更新 更多