NSURLConnection และ Basic HTTP Authentication ใน iOS


85

ฉันจำเป็นต้องวิงวอนขอเริ่มต้นด้วยพื้นฐานGET HTTP request Authenticationนี่จะเป็นครั้งแรกที่ส่งคำขอไปยังเซิร์ฟเวอร์และฉันมีไฟล์username & passwordจึงไม่จำเป็นต้องมีการท้าทายจากเซิร์ฟเวอร์เพื่อขออนุญาต

คำถามแรก:

  1. ไม่NSURLConnectionต้องมีการตั้งค่าให้เป็นซิงโครที่จะทำตรวจสอบสิทธิ์พื้นฐาน? จากคำตอบในโพสต์นี้ดูเหมือนว่าคุณจะไม่สามารถทำการ Auth ขั้นพื้นฐานได้หากคุณเลือกใช้เส้นทาง async

  2. มีใครทราบรหัสตัวอย่างที่แสดงการตรวจสอบสิทธิ์ขั้นพื้นฐานGET requestโดยไม่จำเป็นต้องตอบคำถาม เอกสารของ Appleแสดงตัวอย่าง แต่หลังจากเซิร์ฟเวอร์ได้ส่งคำขอท้าทายให้กับลูกค้าแล้วเท่านั้น

ฉันเป็นส่วนเครือข่ายใหม่ของ SDK และฉันไม่แน่ใจว่าควรใช้คลาสใดในการทำงานนี้ (ฉันเห็นNSURLCredentialคลาส แต่ดูเหมือนว่าจะใช้กับNSURLAuthenticationChallengeหลังจากที่ไคลเอนต์ได้ร้องขอทรัพยากรที่ได้รับอนุญาตจากเซิร์ฟเวอร์เท่านั้น)

คำตอบ:


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 ที่นี่: github.com/ctshryock/MGTwitterEngine
cats โดย

7
สำหรับ base64-encoding ( [authData base64EncodedString]) ให้เพิ่มไฟล์ NSData + Base64.h และ. m จาก Matt Gallagher ไปยัง XCode-Project ของคุณ ( ตัวเลือกการเข้ารหัส Base64 บน Mac และ iPhone )
ลิม

3
NSASCIIStringEncoding จะทำให้ชื่อผู้ใช้หรือรหัสผ่านที่ไม่ใช่ usascii เสียหาย ใช้ NSUTF8StringEncoding แทน
Dirk de Kok

4
base64EncodingWithLineLength ไม่มีในปี 2014 บน NSData ใช้ base64Encoding แทน
bickster

11
@bickster base64Encodingเลิกใช้งานตั้งแต่ iOS 7.0 และ OS X 10.9 ฉันใช้[authData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed]แทน นอกจากนี้ยังมี `NSDataBase64Encoding64CharacterLineLength` หรือNSDataBase64Encoding76CharacterLineLength
Dirk

80

แม้จะตอบคำถามฉันก็ต้องการนำเสนอวิธีแก้ปัญหาซึ่งไม่ต้องการ libs ภายนอกฉันพบในเธรดอื่น:

// 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 Digest Auth มันเป็นการแลกเปลี่ยน
benzado

2
อย่างไรก็ตามนี่คือ "วิธีที่ถูกต้อง" ในการทำ วิธีอื่น ๆ ทั้งหมดคือทางลัด
ลากอส

1
ขอบคุณมาก! @moosgummi
LE SANG

@dom ฉันเคยใช้สิ่งนี้ แต่ด้วยเหตุผลบางประการไม่ได้เรียก RecieveAuthenticationChallenge และฉันได้รับข้อความปฏิเสธการเข้าถึง 403 กลับจากไซต์ ใครรู้ว่ามีอะไรผิดปกติ?
Declan McKenna

ใช่นี่เป็นวิธีเดียวที่ถูกต้องในการทำ และทำให้เกิดการตอบสนอง 401 ในครั้งแรกเท่านั้น คำขอที่ตามมาไปยังเซิร์ฟเวอร์เดียวกันนั้นจะถูกส่งไปพร้อมกับการพิสูจน์ตัวตน
dgatwood

12

นี่คือคำตอบโดยละเอียดโดยไม่มีบุคคลที่สามเกี่ยวข้อง:

โปรดตรวจสอบที่นี่:

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

โปรดแจ้งให้เราทราบความคิดเห็นของคุณเกี่ยวกับเรื่องนี้

ขอบคุณ


วิธีการ base64Encoding ที่คุณใช้ในการแปลง NSData เป็น NSString เลิกใช้แล้ว: - (NSString *)base64Encoding NS_DEPRECATED(10_6, 10_9, 4_0, 7_0);ควรใช้หมวดหมู่ NSDataBase64Encoding แทน
เบ็น

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 29 ธ.ค. 52 เวลา 17:29 น

ไม่ได้ จำกัด ไว้ที่ 80 แต่เป็นตัวเลือกของเมธอด base64EncodingWithLineLength ใน NSData + Base64.h / m ซึ่งคุณสามารถแยกสตริงที่เข้ารหัสของคุณออกเป็นหลายบรรทัดซึ่งมีประโยชน์สำหรับแอพพลิเคชั่นอื่น ๆ เช่นการส่ง nntp ฉันเชื่อว่า 80 ถูกเลือกโดยผู้เขียน twitter engine ให้มีความยาวใหญ่พอที่จะรองรับผลลัพธ์ที่เข้ารหัสผู้ใช้ / รหัสผ่านส่วนใหญ่เป็นหนึ่งบรรทัด


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];
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.