在iOS中,NSURLConnection和基本的HTTP认证是什么?

85

我需要使用基本认证进行初始的GET HTTP请求。这将是第一次向服务器发送此请求,我已经有了用户名和密码,所以不需要服务器授权的挑战。

第一个问题:

  1. 在进行基本身份验证时,NSURLConnection必须设置为同步吗?根据这个帖子中的回答,似乎如果选择异步路线,则无法进行基本身份验证。

  2. 有人知道任何没有需要响应挑战的样例代码来说明在GET请求上进行基本身份验证吗?苹果的文档显示了一个示例,但仅在服务器发出挑战请求给客户端之后才会显示。

我对SDK的网络部分比较新,不确定我应该使用哪些其他类来使其正常工作。(我看到NSURLCredential类,但似乎只用于在客户端从服务器请求经过授权的资源后与NSURLAuthenticationChallenge配合使用)。

8个回答

132

我正在使用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"];

我不相信这种方法需要通过挑战循环,但我可能是错的


2
我没有编写那部分代码,它只是MGTwitterEngine的一部分,是添加到NSData类别中的。请参阅此处的NSData+Base64.h/m:http://github.com/ctshryock/MGTwitterEngine。 - catsby
7
对于base64编码([authData base64EncodedString]),请将Matt Gallagher的NSData+Base64.h和.m文件添加到您的XCode项目中(Mac和iPhone上的Base64编码选项)。 - elim
3
NSASCIIStringEncoding会破坏非US-ASCII编码的用户名或密码。请改用NSUTF8StringEncoding。 - Dirk de Kok
4
2014年的NSData中没有base64EncodingWithLineLength方法,应使用base64Encoding方法代替。请注意不要改变原意。 - bickster
11
自iOS 7.0和OS X 10.9以来,base64Encoding已被弃用。我使用[authData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed]代替它。还可以使用NSDataBase64Encoding64CharacterLineLengthNSDataBase64Encoding76CharacterLineLength - Dirk
显示剩余4条评论

79

即使问题已经得到解答,我仍想呈现解决方案,该方案不需要外部库,我在另一个帖子中找到了该方案:

// 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 {
    ...
}

9
这个解决方案与其他解决方案不太相同:它首先与服务器联系,接收到 401 响应,然后使用正确的凭据进行响应。因此,您浪费了一次往返。但好处是,您的代码将处理其他挑战,例如 HTTP 摘要认证。这是一个权衡。 - benzado
2
无论如何,这是“正确的方法”。其他所有方法都是捷径。 - lagos
@dom 我已经使用过这个,但由于某些原因 didRecieveAuthenticationChallenge 没有被调用,并且我从该网站收到了403访问被拒绝的消息。有人知道出了什么问题吗? - Declan McKenna
是的,这是唯一正确的方法。而且它只会在第一次引起401响应。对于同一服务器的后续请求都将带有身份验证信息。 - dgatwood

13

这里是一个详细的回答,没有第三方参与:

请在这里查看:

//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类别。 - Ben

7
如果您不想导入整个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

3

自从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"];

1
如果您正在使用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:)];

0
你能告诉我为什么在你的示例代码中将编码行长度限制为80的原因吗?我认为HTTP头有一个最大长度,大约是4k(或者可能有些服务器不接受超过这个长度的任何内容)。- Justin Galzic 2009年12月29日17:29
它并没有限制到80,而是在NSData+Base64.h/m中的方法base64EncodingWithLineLength的选项,您可以将编码后的字符串拆分成多行,这对其他应用程序非常有用,例如nntp传输。我相信Twitter引擎作者选择80作为长度足够大以容纳大多数用户/密码编码结果到一行。

0

您可以使用AFNetworking(它是开源的),这是适合我工作的代码。此代码使用基本身份验证发送文件。只需更改URL、电子邮件和密码即可。

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];

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接