2012-01-25 20 views
19

Me gustaría admitir la Autenticación básica HTTP en mi UIWebView.¿Cómo hacer la autenticación en UIWebView correctamente?

Por el momento, yo estoy cancelando pedidos en

webView:shouldStartLoadWithRequest:navigationType: luego manejarlos en mi propia NSURLConnectionDelegate para buscar y proporcionar credenciales si es necesario. Luego uso loadData:MIMEType:textEncodingName:baseURL: para presentar HTML en la vista web. Eso funciona bien para cualquier URL que se pasa al delegado.

Mi problema es que al delegado nunca se le pide elementos incrustados, como imágenes, JavaScript o archivos CSS. Entonces, si tengo una página HTML que hace referencia a una imagen que está protegida con autenticación básica, esa imagen no se puede cargar correctamente. Además, nunca se llama al webView:didFinishLoad:, porque la vista web no pudo cargar completamente la página.

He comprobado el caso con Terra, un navegador de terceros disponible en la App Store, y puede hacer frente por completo a esa situación. Creo que sería posible resolver esto proporcionando mi propio NSURLProtocol, pero parece demasiado complicado. ¿Qué me estoy perdiendo?

+0

Hey NeoNacho, ¿Has descubierto cómo resolver este problema? Tengo el mismo problema: puedo cargar la página html, pero todos los css/javascript nunca se cargan/procesan correctamente. Si tiene alguna pista, comparta :) – Stretch

Respuesta

25

Intente utilizar sharedCredentialStorage para todos los dominios que necesita autenticar.

Aquí se muestra de trabajo para UIWebView se puso a prueba en contra de tener de Windows IIS sólo BasicAuthentication permitió

Esto es cómo agregar sus credenciales del sitio:

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

Su web View se supone que funciona ahora, si no funciona, use el siguiente código para depurar, especialmente revise los mensajes de registro de 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 solución final para la autenticación WebView se basó en la implementación de protocolo personalizado. Todos los protocolos registrados como una pila, por lo que si redefine el protocolo HTTP interceptaría todas las solicitudes provenientes de webView, por lo que debe verificar los atributos asignados con la solicitud entrante y volver a empaquetarla en una nueva solicitud y enviarla de nuevo a través de su propia conexión. Ya que estás en el apilamiento, tu pedido viene de inmediato a ti y tienes que ignorarlo. Así que baja la pila de protocolos a la implementación del protocolo HTTP real, ya que su solicitud no se autentica, obtendrá una solicitud de autenticación. Y después de la autentificación, obtendrá una respuesta real del servidor, por lo que reempaquetará la respuesta y responderá a la solicitud original recibida de webView y eso es todo.

No intente crear nuevas solicitudes o cuerpos de respuestas, solo tiene que volver a enviarlas. El código final sería aproximadamente de 30 a 40 líneas de código y es bastante simple, pero requiere una gran cantidad de depuración y detección.

Desafortunadamente No puedo proporcionar el código aquí, ya que estoy asignado a un proyecto diferente, solo quería decir que mi publicación es incorrecta, es sorprendente cuando el usuario cambia la contraseña.

+0

¿esto sucede automáticamente? Una vez autenticado en un host, parece que no es necesario volver a autenticarse en las solicitudes posteriores. –

+0

Estoy viendo el mismo comportamiento que Max. Una vez autenticado, no me desafiarán a volver a autenticarse nuevamente. Algo está siendo almacenado en mi sesión de simulador iOS, supongo? Traté de restablecer el simulador, pero aún no me desafiaron. Extraño. – gstroup

+0

"El moderador mató el mensaje importante para mí, eliminé mensajes importantes para él. Esta publicación me costó 4 semanas de investigación. Así que que tengas un buen día". Lamento anunciarle que su respuesta original está almacenada en el historial de publicaciones y que ahora se va a recuperar. Gracias por la atención. – Shoe

2

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

8

El secreto para la autenticación básica HTTP usando el cacao es conociendo NSURL y las clases relacionadas.

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

La verdadera magia proviene de NSURLConnection. En palabras de devDocs, "Un objeto NSURLConnection proporciona soporte para realizar la carga de una solicitud de URL". Si quiere cargar una URL en el fondo sin mostrarla, debería usar NSURLConnection. El poder real de la NSURLConnection está en el método

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

El protocolo NSURLConnectionDelegate tiene métodos para responder a las conexiones exitosas, errores fatales, y los desafíos de autenticación. Si está intentando acceder a los datos Protegidos por autenticación básica HTTP, así es como lo hace Cocoa. En este punto, un ejemplo debería traer algo de claridad.

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

Esto crea una URL. Desde la URL se crea una URLRequest. La URLRequest se carga en la vista web. La Solicitud también se usa para hacer una URLConnection. Realmente no usamos la conexión, pero necesitamos recibir notificaciones sobre la autenticación, así que configuramos el delegado. Solo hay dos métodos que necesitamos del delegado.

- (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; 
} 

Cada vez que hay un desafío de autenticación se añade una credencial para el almacenamiento de credenciales. También le dice a la conexión que use el almacenamiento de credenciales.

+0

Solo una pregunta: Veo que está agregando las credenciales en sharedCredentialStorage cuando obtiene el evento didReceiveAuthenticationChallenge. ¿Pero es una buena práctica restablecer las credenciales cada vez que recibe este evento? Solo preguntando, estoy tratando de descubrir cómo funciona SharedCredentialStorage. – NLemay

+0

Probablemente no sea la mejor solución, pero fue funcional y nunca volví a limpiarlo. Si la contraseña no va a cambiar, es probable que la coloque fuera de la llamada de delegado y solo lo haga una vez. Si la credencial va a cambiar, sería bueno buscar la contraseña de usuario actual cuando se reciba la prueba. –

4

Acabo de implementar esto mediante el establecimiento de credenciales de autenticación básicas utilizando un NSMutableURLRequest para el UIWebView. Esto también evita el viaje de ida y vuelta en el que se incurrió al implementar sharedCredentialStorage (por supuesto, hay compensaciones involucradas).

Solución:

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

Usted puede agarrar la categoría NSData + Base 64 que implementa el base64EncodedString para NSData de Matt Gallagher's page (que estaba en el fondo de la entrada de blog cuando lo descargué)

+0

Sólo pensé que agregaría que ya no necesita la categoría NSData + Base64 ya que esa funcionalidad está incluida en la implementación estándar de NSData ahora (sin embargo, una firma de método ligeramente diferente) –

+0

De hecho, consulte los documentos de Apple para la codificación Base64 [aquí] (https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSData_Class/#//apple_ref/occ/instm/NSData/base64EncodedStringWithOptions :) – NSTJ

+5

Al final, define una nueva solicitud de URL '[NSURLRequest basicAuthHTTPURLRequestForUrl: url];' ¿Cómo influyen las definiciones anteriores en esa solicitud? – matteok

Cuestiones relacionadas