NSURLConnection và Xác thực HTTP cơ bản trong iOS


85

Tôi cần gọi một chữ cái đầu GET HTTP requestbằng Basic Authentication. Đây sẽ là lần đầu tiên yêu cầu được gửi đến máy chủ và tôi đã có yêu cầu username & passwordnên không cần thử thách từ máy chủ để ủy quyền.

Câu hỏi đầu tiên:

  1. NSURLConnectionphải được đặt là đồng bộ để thực hiện Xác thực cơ bản không? Theo câu trả lời trên bài đăng này , có vẻ như bạn không thể thực hiện Xác thực cơ bản nếu bạn chọn tuyến không đồng bộ.

  2. Có ai biết về bất kỳ mã mẫu nào minh họa Basic Auth trên a GET requestmà không cần phản hồi thách thức không? Tài liệu của Apple cho thấy một ví dụ nhưng chỉ sau khi máy chủ đưa ra yêu cầu thử thách cho khách hàng.

Tôi là một phần mới của phần mạng của SDK và tôi không chắc mình nên sử dụng lớp nào trong số các lớp khác để làm việc này. (Tôi thấy NSURLCredentiallớp nhưng có vẻ như nó chỉ được sử dụng NSURLAuthenticationChallengesau khi máy khách đã yêu cầu tài nguyên được ủy quyền từ máy chủ).

Câu trả lời:


132

Tôi đang sử dụng kết nối không đồng bộ với MGTwitterEngine và nó đặt ủy quyền trong NSMutableURLRequest( theRequest) như sau:

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

Tôi không tin rằng phương pháp này yêu cầu phải vượt qua vòng thử thách nhưng tôi có thể sai


2
Tôi không viết phần đó, nó chỉ là một phần của MGTwitterEngine, từ một danh mục được thêm vào NSData. Xem NSData + Base64.h / m tại đây: github.com/ctshryock/MGTwitterEngine
catby 29/12/09

7
Đối với mã hóa base64 ( [authData base64EncodedString]), hãy thêm Tệp NSData + Base64.h và .m từ Matt Gallagher vào XCode-Project của bạn ( tùy chọn mã hóa Base64 trên Mac và iPhone ).
elim

3
NSASCIIStringEncoding sẽ làm hỏng tên người dùng hoặc mật khẩu không phải usascii. Sử dụng NSUTF8StringEncoding thay
Dirk de Kok

4
base64EncodingWithLineLength không tồn tại trong năm 2014 trên NSData. Sử dụng base64Encoding để thay thế.
bickster

11
@bickster base64Encodingkhông được dùng nữa kể từ iOS 7.0 và OS X 10.9. Tôi sử dụng [authData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed]thay thế. Cũng có sẵn là `NSDataBase64Encoding64CharacterLineLength` hoặcNSDataBase64Encoding76CharacterLineLength
Dirk

80

Ngay cả câu hỏi đã được trả lời, tôi muốn trình bày giải pháp mà không yêu cầu lib bên ngoài, tôi đã tìm thấy trong một chủ đề khác:

// 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
Điều này không hoàn toàn giống với các giải pháp khác: giải pháp này liên hệ với máy chủ trước tiên, nhận phản hồi 401, sau đó trả lời bằng thông tin xác thực chính xác. Vì vậy, bạn đang lãng phí một chuyến đi khứ hồi. Mặt khác, mã của bạn sẽ xử lý các thách thức khác, chẳng hạn như Xác thực thông báo HTTP. Đó là một sự đánh đổi.
benzado

2
Dù sao thì đây cũng là "cách làm đúng". Tất cả các cách khác là một phím tắt.
lagos

1
Cám ơn rất nhiều! @moosgummi
LÊ SANG

@dom Tôi đã sử dụng cái này nhưng vì một số lý do màRecieveAuthenticationChallenge không được gọi và tôi nhận được thông báo bị từ chối truy cập 403 từ trang web. Có ai biết chuyện gì đã xảy ra không?
Declan McKenna

Vâng, đây là cách chính xác duy nhất để làm điều đó. Và nó chỉ gây ra phản hồi 401 trong lần đầu tiên. Các yêu cầu tiếp theo đến cùng một máy chủ đó được gửi cùng với xác thực.
dgatwood

12

Đây là câu trả lời chi tiết mà không có bên thứ 3 tham gia:

Vui lòng kiểm tra tại đây:

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

Vui lòng cho tôi biết phản hồi của bạn về điều này.

Cảm ơn


Phương thức base64Encoding bạn đang sử dụng để chuyển đổi NSData thành NSString hiện không được dùng nữa: - (NSString *)base64Encoding NS_DEPRECATED(10_6, 10_9, 4_0, 7_0);Tốt hơn nên sử dụng danh mục NSDataBase64Encoding để thay thế.
Ben

7

Nếu bạn không muốn nhập toàn bộ MGTwitterEngine và bạn không thực hiện một yêu cầu không đồng bộ thì bạn có thể sử dụng http://www.chrisumbel.com/article/basic_authentication_iphone_cocoa_touch

Để mã hóa base64 Tên người dùng và mật khẩu Vì vậy, hãy thay thế

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

với

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

sau

bạn sẽ cần phải bao gồm tệp sau

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

Vì NSData :: dataUsingEncoding không được dùng nữa (ios 7.0), bạn có thể sử dụng giải pháp này:

// 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

Nếu bạn đang sử dụng GTMHTTPFetcher cho kết nối của mình, xác thực cơ bản cũng khá dễ dàng. Bạn chỉ cần cung cấp thông tin đăng nhập cho trình tìm nạp trước khi bắt đầu tìm nạp.

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

Bạn có thể cho tôi biết lý do đằng sau việc giới hạn độ dài dòng mã hóa thành 80 trong mã ví dụ của bạn là gì không? Tôi nghĩ rằng các tiêu đề HTTP có độ dài tối đa là 4k (hoặc có thể một số máy chủ không lâu hơn thế). - Justin Galzic 29 tháng 12, 09 lúc 17:29

Nó không giới hạn ở 80, nó là một tùy chọn của phương thức base64EncodingWithLineLength trong NSData + Base64.h / m, nơi bạn có thể chia chuỗi được mã hóa của mình thành nhiều dòng, rất hữu ích cho các ứng dụng khác, chẳng hạn như truyền nntp. Tôi tin rằng 80 được tác giả công cụ twitter chọn là độ dài đủ lớn để chứa hầu hết kết quả được mã hóa người dùng / mật khẩu vào một dòng.


0

Bạn có thể sử dụng AFNetworking (nó là mã nguồn mở), đây là mã phù hợp với tôi. Mã này gửi tệp với xác thực cơ bản. Chỉ cần thay đổi url, email và mật khẩu.

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];
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.