2012-01-25 13 views
19

Desidero supportare l'autenticazione di base HTTP nella mia UIWebView.Come eseguire correttamente l'autenticazione in UIWebView?

Al momento, sto annullando le richieste in

webView:shouldStartLoadWithRequest:navigationType: poi gestire nel mio NSURLConnectionDelegate per controllare e fornire le credenziali, se necessario. Quindi utilizzo loadData:MIMEType:textEncodingName:baseURL: per presentare l'HTML nella visualizzazione Web. Funziona bene per qualsiasi URL che viene passato al delegato.

Il mio problema è che il delegato non viene mai chiamato per elementi incorporati, come immagini, JavaScript o file CSS. Quindi, se ho una pagina HTML che fa riferimento a un'immagine che è protetta con l'autenticazione di base, quell'immagine non può essere caricata correttamente. Inoltre, webView:didFinishLoad: non viene mai chiamato, perché la visualizzazione Web non può caricare completamente la pagina.

Ho controllato il caso con Terra, un browser di terze parti disponibile su App Store, e può far fronte pienamente a questa situazione. Penso che sarebbe possibile risolvere questo fornendo il mio NSURLProtocol, ma sembra troppo complicato. Cosa mi manca?

+0

Hey NeoNacho, hai capito come risolvere questo problema? Ho lo stesso problema: posso caricare la pagina html, ma tutto il css/javascript non viene mai caricato/elaborato correttamente. Se hai qualche suggerimento, condividi :) – Stretch

risposta

25

Prova ad usare sharedCredentialStorage per tutti i domini che devi autenticare.

Qui è campione lavorando per UIWebView è stato testato contro di Windows IIS avendo solo BasicAuthentication abilitato

Questo è come aggiungere le credenziali del sito:

 NSString* login = @"MYDOMAIN\\myname"; 
     NSURLCredential *credential = [NSURLCredential credentialWithUser:login 
                   password:@"mypassword" 
                   persistence:NSURLCredentialPersistenceForSession]; 

     NSURLProtectionSpace *protectionSpace = [[NSURLProtectionSpace alloc] 
               initWithHost:@"myhost" 
               port:80 
               protocol:@"http" 
               realm:@"myhost" // check your web site settigns or log messages of didReceiveAuthenticationChallenge 
               authenticationMethod:NSURLAuthenticationMethodDefault]; 


     [[NSURLCredentialStorage sharedCredentialStorage] setDefaultCredential:credential forProtectionSpace:protectionSpace]; 
     [protectionSpace release];  

tuo WebView dovrebbe funzionare ora, se non funziona usa il prossimo codice per eseguire il debug, in particolare controlla i messaggi di log di didReceiveAuthenticationChallenge.

#import "TheSplitAppDelegate.h" 
    #import "RootViewController.h" 

    @implementation TheSplitAppDelegate 

    @synthesize window = _window; 
    @synthesize splitViewController = _splitViewController; 
    @synthesize rootViewController = _rootViewController; 
    @synthesize detailViewController = _detailViewController; 

    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 
    { 
     // Override point for customization after application launch. 
     // Add the split view controller's view to the window and display. 
     self.window.rootViewController = self.splitViewController; 
     [self.window makeKeyAndVisible]; 

     NSLog(@"CONNECTION: Add credentials"); 

     NSString* login = @"MYDOMAIN\\myname"; 
     NSURLCredential *credential = [NSURLCredential credentialWithUser:login 
                   password:@"mypassword" 
                   persistence:NSURLCredentialPersistenceForSession]; 

     NSURLProtectionSpace *protectionSpace = [[NSURLProtectionSpace alloc] 
               initWithHost:@"myhost" 
               port:80 
               protocol:@"http" 
               realm:@"myhost" // check your web site settigns or log messages of didReceiveAuthenticationChallenge 
               authenticationMethod:NSURLAuthenticationMethodDefault]; 


     [[NSURLCredentialStorage sharedCredentialStorage] setDefaultCredential:credential forProtectionSpace:protectionSpace]; 
     [protectionSpace release];  

     NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"http://myhost/index.html"] 
                   cachePolicy:NSURLRequestReloadIgnoringCacheData 
                  timeoutInterval:12 
             ]; 

     NSLog(@"CONNECTION: Run request"); 
     [[NSURLConnection alloc] initWithRequest:request delegate:self]; 

     return YES; 
    } 

    - (void)applicationWillResignActive:(UIApplication *)application 
    { 

    } 

    - (void)applicationDidEnterBackground:(UIApplication *)application 
    { 

    } 

    - (void)applicationWillEnterForeground:(UIApplication *)application 
    { 

    } 

    - (void)applicationDidBecomeActive:(UIApplication *)application 
    { 

    } 

    - (void)applicationWillTerminate:(UIApplication *)application 
    { 

    } 

    - (void)dealloc 
    { 
     [_window release]; 
     [_splitViewController release]; 
     [_rootViewController release]; 
     [_detailViewController release]; 
     [super dealloc]; 
    } 

    - (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge; 
    { 
     NSLog(@"CONNECTION: got auth challange"); 
     NSString* message = [NSString stringWithFormat:@"CONNECTION: cred cout = %i", [[[NSURLCredentialStorage sharedCredentialStorage] allCredentials] count]]; 
     NSLog(message); 
     NSLog([connection description]); 

     NSLog([NSString stringWithFormat:@"CONNECTION: host = %@", [[challenge protectionSpace] host]]); 
     NSLog([NSString stringWithFormat:@"CONNECTION: port = %i", [[challenge protectionSpace] port]]); 
     NSLog([NSString stringWithFormat:@"CONNECTION: protocol = %@", [[challenge protectionSpace] protocol]]); 
     NSLog([NSString stringWithFormat:@"CONNECTION: realm = %@", [[challenge protectionSpace] realm]]); 
     NSLog([NSString stringWithFormat:@"CONNECTION: authenticationMethod = %@", [[challenge protectionSpace] authenticationMethod]]); 
    } 

    - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error{ 
     // release the connection, and the data object 
     [connection release]; 

     // inform the user 
     NSLog(@"CONNECTION: failed! Error - %@ %@", 
       [error localizedDescription], 
       [[error userInfo] objectForKey:NSURLErrorFailingURLStringErrorKey]); 
    } 

    - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response; 
    { 
     NSLog(@"CONNECTION: received response via nsurlconnection"); 
    } 

    - (BOOL)connectionShouldUseCredentialStorage:(NSURLConnection *)connection; 
    { 
     NSLog(@"CONNECTION: USE!"); 
     return YES; 
    } 


    @end 

La soluzione finale per l'autenticazione WebView era basata sull'implementazione del protocollo personalizzato. Tutti i protocolli registrati come uno stack, quindi se si ridefinisce il protocollo HTTP si intercettano tutte le richieste provenienti da webView, quindi è necessario controllare gli attributi assopiti con la richiesta in arrivo e reimballarli in una nuova richiesta e inviarlo nuovamente tramite la propria connessione. Dato che sei in pila, la tua richiesta viene immediatamente da te e devi ignorarla. In questo modo si riduce lo stack del protocollo all'implementazione del protocollo HTTP reale, dal momento che la richiesta non è autenticata riceverai la richiesta di autenticazione. E dopo authenticaiton riceverai una risposta reale dal server, quindi rispondi alla risposta e rispondi alla richiesta originale ricevuta da webView e il gioco è fatto.

Don; t tenta di creare nuove richieste o corpi di risposta, devi semplicemente inviarli di nuovo. Il codice finale dovrebbe comprendere approssimativamente 30-40 righe di codice ed è piuttosto semplice, ma richiede un sacco di debug e tetsing.

Purtroppo non posso fornire il codice qui, poiché sono già assegnato a un progetto diverso, volevo solo dire che il mio post è sbagliato, si blocca quando l'utente cambia password.

+0

si verifica automaticamente? Una volta autenticato contro un host, sembra che non sia necessario effettuare nuovamente l'autenticazione alle richieste successive. –

+0

Sto vedendo lo stesso comportamento di Max. Una volta autenticato, non mi viene contestato di ri-autenticarsi di nuovo. Immagino che qualcosa venga memorizzato nella mia sessione di simulatore iOS? Ho provato a resettare il simulatore, ma non sono ancora stato sfidato. Strano. – gstroup

+0

"Il moderatore ha ucciso un post importante per me, io uccido post importanti per lui Questo post mi è costato 4 settimane di ricerca, quindi buona giornata". Mi dispiace annunciarti che la tua risposta originale è archiviata nella cronologia postale e verrà recuperata ora. Grazie per l'attenzione. – Shoe

2

Per TKAURLProtocolPro [http://kadao.dir.bg/cocoa.htm] Per SVWebViewController [https://github.com/samvermette/SVWebViewController]

8

Il segreto per l'autenticazione HTTP di base utilizzando il cacao è conoscendo NSURL e le classi correlate.

  • NSURL
  • NSURLRequest/NSMutableURLRequest
  • NSURLConnection
  • NSURLCredential
  • NSURLCredentialStorage
  • NSURLProtectionSpace
  • UIWebView/WebView/NIWebController ecc

La vera magia viene da NSURLConnection. Nelle parole di devDocs, "Un oggetto NSURLConnection fornisce supporto per eseguire il caricamento di una richiesta URL." Se vuoi caricare un URL in background senza visualizzarlo, useresti NSURLConnection. La vera potenza del NSURLConnection è nel metodo

+ (NSURLConnection *)connectionWithRequest:(NSURLRequest *)request delegate:(id <NSURLConnectionDelegate>)delegate 

il protocollo NSURLConnectionDelegate ha metodi per reagire alle connessioni riuscite, errori fatali e sfide autenticazione. Se stai cercando di accedere ai dati protetti da autenticazione di base HTTP, è così che fa Cocoa. A questo punto un esempio dovrebbe portare una certa chiarezza.

//basic HTTP authentication 
NSURL *url = [NSURL URLWithString: urlString]; 
NSMutableURLRequest *request; 
request = [NSMutableURLRequest requestWithURL:url 
           cachePolicy:NSURLRequestReloadIgnoringCacheData 
          timeoutInterval:12]; 
[self.webView openRequest:request]; 
(void)[NSURLConnection connectionWithRequest:request delegate:self]; 

Questo crea un URL. Dall'URL viene creato un URLRequest. La richiesta URL viene quindi caricata nella vista Web. La richiesta viene anche utilizzata per creare una connessione URLC. Non usiamo realmente la connessione, ma abbiamo bisogno di ricevere notifiche sull'autenticazione, quindi impostiamo il delegato. Ci sono solo due metodi di cui abbiamo bisogno dal delegato.

- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge; 
{ 
    NSURLCredential * cred = [NSURLCredential credentialWithUser:@"username" 
                password:@"password" 
               persistence:NSURLCredentialPersistenceForSession]; 
[[NSURLCredentialStorage sharedCredentialStorage]setCredential:cred forProtectionSpace:[challenge protectionSpace]]; 

} 

- (BOOL)connectionShouldUseCredentialStorage:(NSURLConnection *)connection; 
{ 
    return YES; 
} 

Ogni volta che c'è una richiesta di autenticazione una credenziale viene aggiunto alla memoria credenziali. Si comunica inoltre alla connessione di utilizzare la memoria credenziali.

+0

Solo una domanda: vedo che si stanno aggiungendo le credenziali in sharedCredentialStorage quando si ottiene l'evento didReceiveAuthenticationChallenge. Ma è una buona pratica ripristinare le credenziali ogni volta che ottieni questo evento? Sto solo chiedendo, sto cercando di capire come funziona sharedCredentialStorage. – NLemay

+0

Probabilmente non è la soluzione migliore, ma era funzionale e non sono mai tornato a ripulirlo. Se la password non cambierà, è probabile che tu la metta al di fuori della chiamata del delegato e la esegui solo una volta, se la credenziale sta per cambiare potrebbe essere utile cercare la password del nome utente corrente quando viene ricevuta la sfida. –

4

Ho appena implementato questo impostando le credenziali di autenticazione di base utilizzando uno NSMutableURLRequest per il UIWebView. Questo evita anche il viaggio di andata e ritorno durante l'implementazione di sharedCredentialStorage (ovviamente ci sono dei compromessi).

Soluzione:

NSString *url = @"http://www.my-url-which-requires-basic-auth.io" 
    NSString *authStr = [NSString stringWithFormat:@"%@:%@", username, password]; 
    NSData *authData = [authStr dataUsingEncoding:NSASCIIStringEncoding]; 
    NSString *authValue = [NSString stringWithFormat:@"Basic %@", [authData base64EncodedString]]; 
    NSMutableURLRequest *mutableRequest = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:url]]; 
    [mutableRequest setValue:authValue forHTTPHeaderField:@"Authorization"]; 
    NSURLRequest *request = [mutableRequest copy]; 
    NSURLRequest *request = [NSURLRequest basicAuthHTTPURLRequestForUrl:url]; 
    [self.webView loadRequest:request]; 

È possibile afferrare la categoria NSData + Base64 che implementa la base64EncodedString per NSData da Matt Gallagher's page (era in fondo al post del blog quando ho scaricato)

+0

Ho appena pensato di aggiungere che non hai più bisogno della categoria Base64 di NSData + poiché tale funzionalità è inclusa nell'implementazione NSData standard ora (firma del metodo leggermente diversa) –

+0

In effetti, controlla i documenti Apple per la codifica Base64 [qui] (https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSData_Class/#//apple_ref/occ/instm/NSData/base64EncodedStringWithOptions :) – NSTJ

+5

Alla fine si definisce una nuova richiesta di URL '[NSURLRequest basicAuthHTTPURLRequestForUrl: url];' In che modo le precedenti definizioni influenzano persino tale richiesta? – matteok

Problemi correlati