NSURLConnection e autenticazione HTTP di base in iOS


85

Devo richiamare un'iniziale GET HTTP requestcon Basic Authentication. Questa sarebbe la prima volta che la richiesta viene inviata al server e ne ho già, username & passwordquindi non c'è bisogno di una sfida dal server per l'autorizzazione.

Prima domanda:

  1. Deve NSURLConnectionessere impostato come sincrono per eseguire l'autenticazione di base? Secondo la risposta su questo post , sembra che non sia possibile eseguire l'autenticazione di base se si opta per il percorso asincrono.

  2. Qualcuno sa di qualche codice di esempio che illustra l'autenticazione di base su un GET requestsenza la necessità di una risposta di sfida? La documentazione di Apple mostra un esempio, ma solo dopo che il server ha inviato la richiesta di sfida al client.

Sono un po 'nuovo la parte di rete dell'SDK e non sono sicuro di quale delle altre classi dovrei usare per farlo funzionare. (Vedo la NSURLCredentialclasse ma sembra che venga utilizzata solo con NSURLAuthenticationChallengedopo che il client ha richiesto una risorsa autorizzata dal server).

Risposte:


132

Sto usando una connessione asincrona con MGTwitterEngine e imposta l'autorizzazione in NSMutableURLRequest( theRequest) in questo modo:

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

Non credo che questo metodo richieda di passare attraverso il ciclo di sfida, ma potrei sbagliarmi


2
Non ho scritto quella parte, è solo una parte di MGTwitterEngine, da una categoria aggiunta a NSData. Vedi NSData + Base64.h / m qui: github.com/ctshryock/MGTwitterEngine
catsby

7
Per la codifica base64 ( [authData base64EncodedString]) aggiungi il file NSData + Base64.he .m di Matt Gallagher al tuo progetto XCode ( opzioni di codifica Base64 su Mac e iPhone ).
elim

3
NSASCIIStringEncoding corromperà i nomi utente o le password non usascii. Utilizza invece NSUTF8StringEncoding
Dirk de Kok

4
base64EncodingWithLineLength non esiste nell'anno 2014 su NSData. Usa invece base64Encoding.
litigi il

11
@bickster base64Encodingè deprecato da iOS 7.0 e OS X 10.9. Io [authData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed]invece uso . Sono disponibili anche `NSDataBase64Encoding64CharacterLineLength` oNSDataBase64Encoding76CharacterLineLength
Dirk

80

Anche la domanda ha una risposta, voglio presentare la soluzione, che non richiede librerie esterne, ho trovato in un altro thread:

// 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
Non è esattamente la stessa delle altre soluzioni: questa contatta prima il server, riceve una risposta 401, quindi risponde con le credenziali corrette. Quindi stai sprecando un viaggio di andata e ritorno. Al rialzo, il tuo codice gestirà altre sfide, come HTTP Digest Auth. È un compromesso.
benzado

2
Comunque, questo è il "modo corretto" di farlo. Tutti gli altri modi sono una scorciatoia.
lagos

1
Grazie mille! @moosgummi
LE SANG

@dom l'ho usato ma per qualche motivo didRecieveAuthenticationChallenge non viene chiamato e ricevo un messaggio di accesso negato 403 dal sito. Qualcuno sa cosa è andato storto?
Declan McKenna

Sì, questo è l'unico modo corretto per farlo. E provoca solo una risposta 401 la prima volta. Le richieste successive allo stesso server vengono inviate con autenticazione.
dgatwood

12

Ecco una risposta dettagliata senza coinvolgere terze parti:

Si prega di controllare qui:

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

Fammi sapere il tuo feedback su questo.

Grazie


Il metodo base64Encoding che stai usando per convertire NSData in NSString è ora deprecato: - (NSString *)base64Encoding NS_DEPRECATED(10_6, 10_9, 4_0, 7_0);meglio usare invece la categoria NSDataBase64Encoding.
Ben

7

Se non vuoi importare l'intero MGTwitterEngine e non stai facendo una richiesta asincrona, puoi usare http://www.chrisumbel.com/article/basic_authentication_iphone_cocoa_touch

In base64 codificare il nome utente e la password Quindi sostituisci

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

con

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

dopo

dovrai includere il seguente file

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

Poiché NSData :: dataUsingEncoding è deprecato (ios 7.0), puoi utilizzare questa soluzione:

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

Se utilizzi GTMHTTPFetcher per la tua connessione, anche l'autenticazione di base è abbastanza semplice. Devi semplicemente fornire le credenziali al fetcher prima di iniziare il recupero.

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

Puoi dirmi qual è la ragione dietro la limitazione della lunghezza della riga di codifica a 80 nel tuo codice di esempio? Ho pensato che le intestazioni HTTP abbiano una lunghezza massima di qualcosa come 4k (o forse alcuni server non impiegano più di questo). - Justin Galzic 29 dicembre 2009 alle 17:29

Non è limitato a 80, è un'opzione del metodo base64EncodingWithLineLength in NSData + Base64.h / m, dove puoi dividere la tua stringa codificata in più righe, utile per altre applicazioni, come la trasmissione nntp. Credo che 80 sia scelto dall'autore del motore di Twitter per essere una lunghezza abbastanza grande da contenere la maggior parte dei risultati codificati utente / password su una riga.


0

Puoi usare AFNetworking (è opensource), ecco il codice che ha funzionato per me. Questo codice invia il file con l'autenticazione di base. Basta cambiare URL, email e password.

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];
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.