NSURLConnection i podstawowe uwierzytelnianie HTTP w systemie iOS

85

Muszę wywołać inicjał GET HTTP requestw Basicu Authentication. To byłby pierwszy raz, gdy żądanie jest wysyłane do serwera, a ja już je mam, username & passwordwięc nie ma potrzeby wezwania serwera do autoryzacji.

Pierwsze pytanie:

  1. Czy NSURLConnectionmusi być ustawione jako synchroniczne, aby wykonać uwierzytelnianie podstawowe? Zgodnie z odpowiedzią w tym poście wydaje się, że nie możesz wykonać podstawowego uwierzytelnienia, jeśli zdecydujesz się na trasę asynchroniczną.

  2. Czy ktoś zna jakiś przykładowy kod, który ilustruje uwierzytelnianie podstawowe GET requestbez potrzeby odpowiedzi na wezwanie? Dokumentacja firmy Apple pokazuje przykład, ale dopiero po wysłaniu przez serwer żądania wezwania do klienta.

Jestem trochę nowy w sieciowej części SDK i nie jestem pewien, której z innych klas powinienem użyć, aby to działało. (Widzę tę NSURLCredentialklasę, ale wygląda na to, że jest używana tylko wtedy, NSURLAuthenticationChallengegdy klient zażąda autoryzowanego zasobu z serwera).

Alexi Groove
źródło

Odpowiedzi:

132

Używam asynchronicznego połączenia z MGTwitterEngine i ustawia autoryzację w NSMutableURLRequest( theRequest) w następujący sposób:

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

Nie wierzę, że ta metoda wymaga przejścia przez pętlę wyzwań, ale mogę się mylić

catsby
źródło
2
Nie napisałem tej części, to tylko część MGTwitterEngine, z kategorii dodanej do NSData. Zobacz NSData + Base64.h / m tutaj: github.com/ctshryock/MGTwitterEngine
cats do
7
Aby uzyskać kodowanie base64 ( [authData base64EncodedString]), dodaj pliki NSData + Base64.h i .m od Matta Gallaghera do swojego XCode-Project ( opcje kodowania Base64 na komputerach Mac i iPhone ).
elim
3
NSASCIIStringEncoding uszkodzi nazwy użytkownika lub hasła inne niż usascii. Zamiast tego użyj NSUTF8StringEncoding
Dirk de Kok,
4
base64EncodingWithLineLength nie istnieje w roku 2014 w NSData. Zamiast tego użyj base64Encoding.
bickster
11
@bickster base64Encodingjest przestarzały od wersji iOS 7.0 i OS X 10.9. Używam [authData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed]zamiast tego. Dostępne są również `NSDataBase64Encoding64CharacterLineLength` lubNSDataBase64Encoding76CharacterLineLength
Dirk
80

Nawet na pytanie mam odpowiedź, chcę przedstawić rozwiązanie, które nie wymaga zewnętrznych bibliotek, znalazłem w innym wątku:

// 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 {
    ...
}
dom
źródło
9
To nie jest dokładnie to samo, co inne rozwiązania: najpierw kontaktuje się z serwerem, otrzymuje odpowiedź 401, a następnie odpowiada z poprawnymi poświadczeniami. Więc marnujesz podróż w obie strony. Z drugiej strony Twój kod poradzi sobie z innymi wyzwaniami, takimi jak uwierzytelnianie HTTP Digest Auth. To kompromis.
benzado
2
W każdym razie jest to „właściwy sposób”, aby to zrobić. Wszystkie inne sposoby to skrót.
Lagos
1
Dzięki wielkie! @moosgummi
LE SANG
@dom Użyłem tego, ale z jakiegoś powodu didRecieveAuthenticationChallenge nie jest wywoływany i otrzymuję komunikat o odmowie dostępu 403 z powrotem z witryny. Czy ktoś wie, co poszło nie tak?
Declan McKenna
Tak, to jedyny właściwy sposób, aby to zrobić. I wywołuje odpowiedź 401 tylko za pierwszym razem. Kolejne żądania do tego samego serwera są wysyłane z uwierzytelnieniem.
dgatwood
12

Oto szczegółowa odpowiedź bez udziału strony trzeciej:

Proszę sprawdzić tutaj:

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

Uprzejmie daj mi znać swoją opinię na ten temat.

Dzięki

user3045072
źródło
Metoda base64Encoding, której używasz do konwertowania NSData na NSString, jest teraz przestarzała: - (NSString *)base64Encoding NS_DEPRECATED(10_6, 10_9, 4_0, 7_0);lepiej jest zamiast tego użyć kategorii NSDataBase64Encoding.
Ben
7

Jeśli nie chcesz importować całego MGTwitterEngine i nie wykonujesz asynchronicznego żądania Następnie możesz użyć http://www.chrisumbel.com/article/basic_authentication_iphone_cocoa_touch

Aby zakodować base64 nazwę użytkownika i hasło, zamień

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

z

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

po

będziesz musiał dołączyć następujący plik

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
Łukasz
źródło
3

Ponieważ NSData :: dataUsingEncoding jest przestarzałe (ios 7.0), możesz użyć tego rozwiązania:

// 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"];
Artem Zaytsev
źródło
1

Jeśli używasz GTMHTTPFetcher do połączenia, podstawowe uwierzytelnianie jest również dość łatwe. Wystarczy podać dane uwierzytelniające do modułu pobierania przed rozpoczęciem pobierania.

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:)];
John MP Knox
źródło
0

Czy możesz mi powiedzieć, jaki jest powód ograniczenia długości linii kodowania do 80 w przykładowym kodzie? Myślałem, że nagłówki HTTP mają maksymalną długość około 4k (a może niektóre serwery nie zajmują więcej czasu). - Justin Galzic 29 grudnia 2009 o 17:29

Nie ogranicza się do 80, jest to opcja metody base64EncodingWithLineLength w NSData + Base64.h / m, w której można podzielić zakodowany ciąg na wiele linii, co jest przydatne w innych aplikacjach, takich jak transmisja nntp. Uważam, że autor silnika Twittera wybrał 80 jako wystarczająco dużą długość, aby pomieścić większość wyników zakodowanych przez użytkownika / hasło w jednej linii.

Gaius Parx
źródło
0

Możesz użyć AFNetworking (jest to opensource), oto kod, który działał dla mnie. Ten kod wysyła plik z podstawowym uwierzytelnieniem. Po prostu zmień adres URL, adres e-mail i hasło.

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];
omanosoft
źródło