NSURLConnection y autenticación HTTP básica en iOS

85

Necesito invocar una inicial GET HTTP requestcon Basic Authentication. Esta sería la primera vez que se envía la solicitud al servidor y ya la tengo, username & passwordpor lo que no es necesario que el servidor impugne la autorización.

Primera pregunta:

  1. ¿ NSURLConnectionTiene que estar configurado como síncrono para realizar la autenticación básica? Según la respuesta de esta publicación , parece que no puede realizar la autenticación básica si opta por la ruta asíncrona.

  2. ¿Alguien sabe de algún código de muestra que ilustre la autenticación básica en un GET requestsin la necesidad de una respuesta de desafío? La documentación de Apple muestra un ejemplo, pero solo después de que el servidor haya emitido la solicitud de desafío al cliente.

Soy un poco nuevo en la parte de redes del SDK y no estoy seguro de cuál de las otras clases debería usar para que esto funcione. (Veo la NSURLCredentialclase, pero parece que se usa solo NSURLAuthenticationChallengedespués de que el cliente haya solicitado un recurso autorizado del servidor).

Alexi Groove
fuente

Respuestas:

132

Estoy usando una conexión asincrónica con MGTwitterEngine y establece la autorización en el NSMutableURLRequest( theRequest) así:

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

No creo que este método requiera pasar por el ciclo de desafío, pero podría estar equivocado

catsby
fuente
2
No escribí esa parte, es solo parte de MGTwitterEngine, de una categoría agregada a NSData. Vea NSData + Base64.h / m aquí: github.com/ctshryock/MGTwitterEngine
cats por
7
Para codificación base64 ( [authData base64EncodedString]), agregue el archivo NSData + Base64.hy .m de Matt Gallagher a su XCode-Project ( opciones de codificación Base64 en Mac y iPhone ).
elim
3
NSASCIIStringEncoding dañará los nombres de usuario o las contraseñas que no sean usascii. Use NSUTF8StringEncoding en su lugar
Dirk de Kok
4
base64EncodingWithLineLength no existe en el año 2014 en NSData. En su lugar, utilice base64Encoding.
bickster
11
@bickster base64Encodingestá obsoleto desde iOS 7.0 y OS X 10.9. Yo uso [authData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed]en su lugar. También están disponibles `NSDataBase64Encoding64CharacterLineLength` oNSDataBase64Encoding76CharacterLineLength
Dirk
80

Incluso la pregunta está respondida, quiero presentar la solución, que no requiere bibliotecas externas, encontré en otro hilo:

// 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
fuente
9
Esto no es exactamente lo mismo que las otras soluciones: primero contacta con el servidor, recibe una respuesta 401 y luego responde con las credenciales correctas. Entonces estás desperdiciando un viaje de ida y vuelta. Por el lado positivo, su código manejará otros desafíos, como HTTP Digest Auth. Es una compensación.
benzado 29/11/12
2
De todos modos, esta es la "forma correcta" de hacerlo. Todas las otras formas son un atajo.
lagos
1
¡Muchas gracias! @moosgummi
LE SANG
@dom He usado esto, pero por alguna razón no se está llamando a didRecieveAuthenticationChallenge y recibo un mensaje de acceso denegado 403 desde el sitio. ¿Alguien sabe qué salió mal?
Declan McKenna
Sí, esta es la única forma correcta de hacerlo. Y solo provoca una respuesta 401 la primera vez. Las solicitudes posteriores a ese mismo servidor se envían con autenticación.
dgatwood
12

Aquí hay una respuesta detallada sin terceros involucrados:

Por favor marque aquí:

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

Por favor déjeme saber sus comentarios al respecto.

Gracias

usuario3045072
fuente
El método base64Encoding que está utilizando para convertir NSData a NSString ahora está obsoleto: es - (NSString *)base64Encoding NS_DEPRECATED(10_6, 10_9, 4_0, 7_0);mejor usar la categoría NSDataBase64Encoding en su lugar.
Ben
7

Si no desea importar todo MGTwitterEngine y no está realizando una solicitud asincrónica, puede usar http://www.chrisumbel.com/article/basic_authentication_iphone_cocoa_touch

Para codificar en base64 el nombre de usuario y la contraseña, reemplace

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

con

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

después

deberás incluir el siguiente archivo

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
Luke
fuente
3

Dado que NSData :: dataUsingEncoding está obsoleto (ios 7.0), puede usar esta solución:

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

Si está utilizando GTMHTTPFetcher para su conexión, la autenticación básica también es bastante fácil. Simplemente debe proporcionar la credencial al buscador antes de comenzar la búsqueda.

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
fuente
0

¿Puede decirme cuál es la razón detrás de limitar la longitud de la línea de codificación a 80 en su código de ejemplo? Pensé que los encabezados HTTP tienen una longitud máxima de algo así como 4k (o tal vez algunos servidores no tardan más que eso). - Justin Galzic 29 de diciembre de 2009 a las 17:29

No se limita a 80, es una opción del método base64EncodingWithLineLength en NSData + Base64.h / m, donde puede dividir su cadena codificada en varias líneas, lo cual es útil para otras aplicaciones, como la transmisión nntp. Creo que 80 es elegido por el autor del motor de Twitter para tener una longitud lo suficientemente grande como para acomodar la mayoría de los resultados codificados por usuario / contraseña en una línea.

Gaius Parx
fuente
0

Puede usar AFNetworking (es de código abierto), aquí está el código que funcionó para mí. Este código envía un archivo con autenticación básica. Simplemente cambie la URL, el correo electrónico y la contraseña.

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
fuente