Pregunta

Alguien ha integrado una aplicación de iPhone con un proveedor de Shibboleth de identidad? Googlear no llegó a nada por lo que pido a los gurús directamente.

Si no ha sido previamente dones, ¿es posible hacerlo?

¿Fue útil?

Solución

La respuesta a ambas preguntas es "Sí".

Soy un tipo Java, por lo que se pidieron hace dos semanas a:

  • Aprender Objective-C
  • Escribir un iPhone App nativa
  • autenticado mediante programación con Shibboleth
  • Descargar un archivo de datos Shibboleth pantalla protegido

... Fue un poco desalentador. Compuesto que con la ausencia de mensajes en el foro para ayudar a cabo me ha llevado a compartir mi experiencia.

He aquí un resumen seguido de un código de ejemplo con suerte muy útil. Por favor vote por mi respuesta si esto ayuda! Merece la pena un par de semanas de mi tiempo:)

Para una aplicación en el iPhone para descargar recursos Shibbolized, las siguientes necesidades sucedan:

  1. Utilice las API URL en Cocoa para enviar la solicitud HTTP para el recurso en cuestión.
  2. Implementar una clase delegada para la solicitud a:
  3. responden a la SP re-directo a la IdP (cortesía automática de cacao)
  4. Responder a los retos de certificados de confianza del servidor
  5. Responder a los retos de credenciales de usuario
  6. Responder a errores (si es necesario)
  7. Recibe "modelo vinculante" de IdP para el usuario autenticado, un formulario HTML que re-dirige al usuario a la SP con dos parámetros
  8. programación POST HTTP los dos parámetros de la IdP de nuevo a la SP.
  9. Las cookies se almacenan de forma automática y hacia delante de nuevo cortesía de cacao
  10. Implementar un segundo URL de solicitud delegado para recibir la solicitud original de datos.

Aquí hay algunas referencias útiles de Apple y Shibboleth:

Y espero que pueda incluir toda la fuente para una demostración rápida.

ApplicationDelegate.h
----------
#import <UIKit/UIKit.h>
#import "ConsoleViewController.h"

/*
 The application delegate will hold references to the application's UIWindow and a ConsoleViewController.
 The console does all of the interesting Shibboleth activities.
*/
@interface ApplicationDelegate : NSObject <UIApplicationDelegate> {

 UIWindow *window;
 ConsoleViewController *consoleViewController;
}


@end

ApplicationDelegate.m
----------
#import "ApplicationDelegate.h"
#import "ConsoleViewController.h"

/*
 The implementation for the ApplicationDelegate initializes the console view controller and assembles everything.
 The console does all of the interesting Shibboleth activities.
 */
@implementation ApplicationDelegate


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

 // Initialize the console.
 consoleViewController = [[ConsoleViewController alloc] init];

 window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
 [window setBackgroundColor:[UIColor lightGrayColor]];
 [window addSubview:[consoleViewController view]];

 [window makeKeyAndVisible];
}


- (void)dealloc {
    [window release];
 [ConsoleViewController release];
    [super dealloc];
}


@end

ConsoleController.h
----------
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>

/*
 The ConsoleViewController's interface declares references to the network data used in negotiating with Shibboleth
 and a UITextView used to display the final result or errors.
 */
@interface ConsoleViewController : UIViewController {

 NSMutableData *responseData;
 NSString *responseString;
 UITextView *console;
}

@end

ConsoleController.m
----------
#import "ApplicationDelegate.h"
#import "ConsoleViewController.h"


/*
 This delegate is used when making the second HTTP request with Shibboleth.  If you're just getting here, start
 by reading the comments for ConsoleViewController below.

 All we need to do now is receive the response from the SP and display it.
 If all goes well, this should be the secured page originally requested.
 */
@interface AuthenticationRedirectDelegate : NSObject {

 NSMutableData *authResponseData;
 NSString *authResponseString;
 UITextView *console;
}

@property (nonatomic retain) UITextView *console;

@end


/*
 Refer to the comments for the interface above.
 */
@implementation AuthenticationRedirectDelegate

@synthesize console;

-(id)init {
 authResponseData = [[NSMutableData alloc] retain];
 return self;
}


- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
 [authResponseData setLength:0];
}


- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
 [authResponseData appendData:data];
}


- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
 [console setText:[error localizedDescription]]; 
}


/*
 Once the data is received from Shibboleth's SP, display it.
 */
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {  

 authResponseString = [[NSString alloc] initWithData:authResponseData encoding:NSUTF8StringEncoding]; 
 [console setText:authResponseString]; 
 [connection release];
}


@end


/*
 The implementation of the ConsoleViewController, and AuthenticationRedirectDelegate above, contain the real logic of
 this Shibboleth exercise.  The ConsoleViewController performs the following:
 1. Prepare the initial HTTP request to a Shibboleth protected resource.
 2. Act as the delegate whilst Cocoa's URL Loading API receives the HTTP Response.
 NOTE: We instruct Cocoa in advance to take care of the SP redirecting to the IdP, accepting the server certificate,
 and submitting the user credentials
 3. Once the HTTP Response is finished loading, parse the <form action, RelayState and SAMLResponse from the IdP's
 response
 4. Call a utility method to prepare a second HTTP POST Request to the <form action/SP with the IdP's parameters
 NOTE: We do not need to transfer over any of Shibboleth's cookies, since Cocoa is doing this automatically
 5. Use a new instance of AuthenticationRedirectDelegate to receive the POST's response, which should be the secured
 page originally requested.
 6. Display the final content in the UITextView known as console.
 */
@implementation ConsoleViewController


/*
 A handy utility method for extracting a substring marked by two provided token strings.
 Used in parsing the HTML form returned by the IdP after the first HTTP Request.
 */
+(id)substringFromString:(NSString *)source BetweenOpenToken:(NSString *)openToken AndCloseToken:(NSString *)closeToken {

 NSUInteger l = [source length];
 NSUInteger openTokenLen = [openToken length];

 NSUInteger openTokenLoc = ([source rangeOfString:openToken]).location;
 NSUInteger valueLoc = openTokenLoc + openTokenLen;
 NSRange searchRange = NSMakeRange(valueLoc, l - valueLoc);
 NSUInteger closeTokenLoc = ([source rangeOfString:closeToken options:NSCaseInsensitiveSearch range:searchRange]).location;
 searchRange = NSMakeRange(valueLoc, closeTokenLoc - valueLoc);
 NSString *result = [source substringWithRange:searchRange];

 return result;
}


/*
 This function takes the three properties returned by the IdP after the first HTTP request and 
 HTTP POSTs them to the SP as specified by the IdP in the "url" parameter.
 */
-(void)authReturnTo:(NSURL *)url WithRelay:(NSString *)relayState AndSAML:(NSString *)samlResponse {

 // Here we assemble the HTTP POST body as usual.
 NSString *preBody = [[NSString alloc] initWithString:@"RelayState="];
 preBody = [preBody stringByAppendingString:relayState];
 preBody = [preBody stringByAppendingString:@"&"];
 preBody = [preBody stringByAppendingString:@"SAMLResponse="];
 preBody = [preBody stringByAppendingString:samlResponse];

 /* The SAMLResponse parameter contains characters (+) that the SP expects to be URL encoded.
  Here we simply manually URL encode those characters.  You may wish to harden this with proper
  URL encoding for production use.
  */
 NSString *httpBody = [preBody stringByReplacingOccurrencesOfString:@"+" withString:@"%2B"];
 NSData *httpBodyData = [httpBody dataUsingEncoding:NSUTF8StringEncoding];

 NSString *httpContentLength = [NSString stringWithFormat:@"%d", [httpBodyData length]];

 NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url
              cachePolicy:NSURLRequestReloadIgnoringCacheData
              timeoutInterval:12.0];
 [request setHTTPMethod:@"POST"];
 [request setValue:httpContentLength forHTTPHeaderField:@"Content-Length"];
 [request setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];

 [request setHTTPBody:httpBodyData];

 // Submit the HTTP POST using the second delegate class to receive the response
 AuthenticationRedirectDelegate *delegate = [[AuthenticationRedirectDelegate alloc] init];
 delegate.console=console;
 [[NSURLConnection alloc] initWithRequest:request delegate:delegate];
}


/*
 When this UIViewController finishes loading, automatically prepare and send a request to the Shibboleth SP Web Server
 for a secured resource.
 */
- (void)viewDidLoad {
 [super viewDidLoad];

 console = [[UITextView alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
 [[self view] addSubview:console];

 responseData = [[NSMutableData data] retain];

 // TODO: Enter your own URL for a Shibboleth secured resource.
 NSURL *url = [NSURL URLWithString:@"<URL>"];

 NSURLRequest *request = [NSURLRequest requestWithURL:url
       cachePolicy:NSURLRequestUseProtocolCachePolicy
       timeoutInterval:12.0];

 [[NSURLConnection alloc] initWithRequest:request delegate:self];

 /* Control flows to the delegate methods below */
}


/*
 Refer to Apple's docs on the URL Loading System for details.
 http://developer.apple.com/mac/library/DOCUMENTATION/Cocoa/Conceptual/URLLoadingSystem/URLLoadingSystem.html
 */
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
  [responseData setLength:0];
}


/*
 Refer to Apple's docs on the URL Loading System for details.
 http://developer.apple.com/mac/library/DOCUMENTATION/Cocoa/Conceptual/URLLoadingSystem/URLLoadingSystem.html
 */
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
 [responseData appendData:data];
}

/*
 This implementation in the delegate let's Cocoa trust my SP Web Server's self-signed certificate.
 TODO: You will want to harden this for production use.

 Refer to Apple's docs on the URL Loading System for details.
 http://developer.apple.com/mac/library/DOCUMENTATION/Cocoa/Conceptual/URLLoadingSystem/URLLoadingSystem.html
 */
- (BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace {
 return [protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust] || [protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodHTTPBasic];
}


/*
 This implementation for the delegate does two things:
 1. Respond to challenges for my server's self-signed certificate
 2. Respond to the IdP's challenge for the username and password.
 TODO: Enter your own username and password here.
 Refer to Apple's docs on the URL Loading System for details.
 http://developer.apple.com/mac/library/DOCUMENTATION/Cocoa/Conceptual/URLLoadingSystem/URLLoadingSystem.html
 */
- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {
 // TODO: Enter the correct username and password below.
 /*
  WARNING: Using an incorrect user name and password will result in your application being re-challenged
  by the IdP.  Cocoa will return to this function in a never-ending loop.  This can result in the message
  "NSPosixErrorDomain Too many open files".  You'll need to perform additional coding to handle this.
  */
 if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust])
  [challenge.sender useCredential:[NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust] forAuthenticationChallenge:challenge];
 else if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodHTTPBasic])
  [challenge.sender useCredential:[NSURLCredential credentialWithUser:@"<USERNAME>" password:@"<PASSWORD>" persistence:NSURLCredentialPersistenceNone] forAuthenticationChallenge:challenge];
 else
  [challenge.sender continueWithoutCredentialForAuthenticationChallenge:challenge];
}


/*
 You may wish to add more code here to log errors.

 Refer to Apple's docs on the URL Loading System for details.
 http://developer.apple.com/mac/library/DOCUMENTATION/Cocoa/Conceptual/URLLoadingSystem/URLLoadingSystem.html
 */
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
 [console setText:[error localizedDescription]];
}


/*
 Once Cocoa has received a (hopefully) authenticated response from the IdP, we parse out the relevant pieces and prepare to
 HTTP POST them back to the SP as specified by the IdP in the <form action attribute.

 Refer to Apple's docs on the URL Loading System for details.
 http://developer.apple.com/mac/library/DOCUMENTATION/Cocoa/Conceptual/URLLoadingSystem/URLLoadingSystem.html
 */
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {  
 [connection release];
 responseString = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding];

 if([responseString rangeOfString:@"SAMLResponse"].length < 1)
 {
  [console setText:[@"Unexpected response:\n]n" stringByAppendingString:responseString]];
  return;
 }

 NSString *relayState = [ConsoleViewController substringFromString:responseString BetweenOpenToken:@"RelayState\" value=\"" AndCloseToken:@"\"/>"];
 NSString *SAMLResponse = [ConsoleViewController substringFromString:responseString BetweenOpenToken:@"SAMLResponse\" value=\"" AndCloseToken:@"\"/>"];
 NSString *formAction = [ConsoleViewController substringFromString:responseString BetweenOpenToken:@"<form action=\"" AndCloseToken:@"\""];
 NSURL *formActionURL = [[NSURL alloc] initWithString:formAction];
 [self authReturnTo:formActionURL WithRelay:relayState AndSAML:SAMLResponse];
}


@end

Otros consejos

Me las arreglé para hacer precisamente eso, pero me tomó algún tiempo para entender cada paso del proceso y de reproducir perfectamente. Si tengo tiempo, yo podría escribir un tutorial detallado, porque no he encontrado ninguna ayuda para muchos de los problemas que tengo. Lo que pasa es que también depende de la página web que desea conectarse, por lo que tal vez la suya no sigue el mismo camino que la mía (el proceso es el mismo que el descrito aquí ).

Para ver todas las solicitudes disparada por mi navegador (Chrome) para conectar, he utilizado el panel de herramientas de desarrollo de la red, con 'Preservar log' marcada.

Un par de consejos:

  • 1 °) Que necesita para obtener "_idp_authn_lc_key ..." cookie. Hay una petición que establece que para usted, encontrarlo.

  • 2 °) Es necesario el boleto de entrada (LT -...). Es probable que encuentres en el cuerpo de la página que pide sus credenciales.

  • 3 °) Se necesita un ticket de servicio (ST -...). Una vez más, se encuentra en la página que la petición anterior regresó.

  • 4 °) Es necesario SAMLResponse. Una vez más, se encuentra en la página que la petición anterior regresó.

  • 5 °) Por último, puede conectarse mediante el envío de vuelta SAMLResponse al proveedor de servicios. Usted debe tener cuidado de la codificación, aquí. Tenía unos cuantos '+' o '=' que tenía que cambiar a '% 2B' y '% 3D'. Se le dará una cookie "_idp_session", que le permitirá volver a conectar sin todo este lío.

Si alguien trata de hacer lo mismo, yo estaría feliz de ayudar! Me acaba de enviar un mensaje.

He implementado con éxito utilizando la solución de EC como punto de partida. La única otra cosa que me gustaría añadir es que realmente hay que prestar atención a mantener una única petición de ir a la vez. En nuestra implementación del proceso de autenticación conseguiría confundido entre múltiples peticiones asíncronas que se ejecutan simultáneamente. El uso del acelerador NSOperation la cola parecía funcionar muy bien para mí.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top