NSURLConnection e autenticação HTTP básica no iOS

85

Preciso invocar uma inicial GET HTTP requestcom Basic Authentication. Esta seria a primeira vez que a solicitação é enviada ao servidor e eu já tenho, username & passwordentão não há necessidade de um desafio do servidor para autorização.

Primeira pergunta:

  1. Precisa NSURLConnectionser definido como síncrono para fazer a autenticação básica? De acordo com a resposta neste post , parece que você não pode fazer a autenticação básica se optar pela rota assíncrona.

  2. Alguém sabe de algum código de amostra que ilustra a autenticação básica em um GET requestsem a necessidade de uma resposta de desafio? A documentação da Apple mostra um exemplo, mas apenas depois que o servidor emitiu a solicitação de desafio para o cliente.

Sou meio que novo na parte de rede do SDK e não tenho certeza de qual das outras classes devo usar para fazer isso funcionar. (Eu vejo a NSURLCredentialclasse, mas parece que ela é usada somente NSURLAuthenticationChallengedepois que o cliente solicitou um recurso autorizado do servidor).

Alexi Groove
fonte

Respostas:

132

Estou usando uma conexão assíncrona com MGTwitterEngine e ele define a autorização no NSMutableURLRequest( theRequest) assim:

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

Não acredito que este método exija passar pelo loop do desafio, mas posso estar errado

catsby
fonte
2
Eu não escrevi essa parte, é apenas parte do MGTwitterEngine, de uma categoria adicionada ao NSData. Consulte NSData + Base64.h / m aqui: github.com/ctshryock/MGTwitterEngine
catsby
7
Para codificação base64 ( [authData base64EncodedString]), adicione os arquivos NSData + Base64.he .m de Matt Gallagher ao seu projeto XCode ( opções de codificação Base64 no Mac e iPhone ).
elim
3
NSASCIIStringEncoding corromperá nomes de usuário ou senhas não usuais. Em vez disso, use NSUTF8StringEncoding
Dirk de Kok
4
base64EncodingWithLineLength não existe no ano de 2014 em NSData. Em vez disso, use base64Encoding.
bickster
11
@bickster base64Encodingestá obsoleto desde iOS 7.0 e OS X 10.9. Eu uso em [authData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed]vez disso. Também estão disponíveis `NSDataBase64Encoding64CharacterLineLength` ouNSDataBase64Encoding76CharacterLineLength
Dirk
80

Mesmo a pergunta sendo respondida, eu quero apresentar a solução, que não requer libs externas, encontrei em outro tópico:

// 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
fonte
9
Isso não é exatamente o mesmo que as outras soluções: isso contata o servidor primeiro, recebe uma resposta 401 e, em seguida, responde com as credenciais corretas. Então você está perdendo uma viagem de ida e volta. Por outro lado, seu código lidará com outros desafios, como HTTP Digest Auth. É uma troca.
benzado
2
De qualquer forma, essa é a "maneira correta" de fazer isso. Todas as outras formas são um atalho.
lagos
1
Muito obrigado! @moosgummi
LE
@dom Eu usei isso, mas por algum motivo didRecieveAuthenticationChallenge não está sendo chamado e estou recebendo uma mensagem de acesso negado de 403 do site. Alguém sabe o que deu errado?
Declan McKenna
Sim, esta é a única maneira correta de fazer isso. E só causa uma resposta 401 na primeira vez. As solicitações subsequentes para esse mesmo servidor são enviadas com autenticação.
dgatwood
12

Aqui está uma resposta detalhada sem terceiros envolvidos:

Verifique aqui:

//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, deixe-me saber seu feedback sobre isso.

obrigado

user3045072
fonte
O método base64Encoding que você está usando para converter NSData em NSString agora está obsoleto: - (NSString *)base64Encoding NS_DEPRECATED(10_6, 10_9, 4_0, 7_0);Melhor usar a categoria NSDataBase64Encoding.
Ben
7

Se você não deseja importar todo o MGTwitterEngine e não está fazendo uma solicitação assíncrona, você pode usar http://www.chrisumbel.com/article/basic_authentication_iphone_cocoa_touch

Para codificar o nome de usuário e a senha em base64, substitua

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

com

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

depois de

você precisará incluir o seguinte arquivo

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
Lucas
fonte
3

Como NSData :: dataUsingEncoding está obsoleto (ios 7.0), você pode usar esta solução:

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

Se você estiver usando GTMHTTPFetcher para sua conexão, a autenticação básica também é bastante fácil. Você simplesmente precisa fornecer a credencial ao buscador antes de iniciar a busca.

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

Você pode me dizer qual é a razão por trás de limitar o comprimento da linha de codificação para 80 em seu código de exemplo? Eu pensei que os cabeçalhos HTTP têm um comprimento máximo de algo como 4k (ou talvez alguns servidores não demorem mais do que isso). - Justin Galzic 29 de dezembro de 2009 às 17:29

Não se limita a 80, é uma opção do método base64EncodingWithLineLength em NSData + Base64.h / m, onde você pode dividir sua string codificada em várias linhas, o que é útil para outro aplicativo, como transmissão nntp. Acredito que 80 foi escolhido pelo autor do mecanismo do Twitter para ter um comprimento grande o suficiente para acomodar a maioria dos resultados codificados por usuário / senha em uma linha.

Gaius Parx
fonte
0

Você pode usar AFNetworking (é código aberto), aqui está o código que funcionou para mim. Este código envia arquivo com autenticação básica. Basta alterar o url, o email e a senha.

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
fonte