2009-12-20 11 views
8

Czy ktoś zintegrował aplikację na iPhone z dostawcą tożsamości Shibboleth? Googling nie wymyślił niczego, więc pytam bezpośrednio guru.Integracja aplikacji iPhone z Shibboleth

Jeśli nie zostało wcześniej wykonane, czy jest to możliwe?

+0

Czy mówisz o aplikacji internetowej lub natywnej aplikacji? –

+0

natywna aplikacja; lub czy strona internetowa iPhone'a może uwierzytelnić natywną aplikację? – user353829

Odpowiedz

15

Odpowiedź na oba pytania brzmi "tak".

Jestem facetem Java, więc poprosił ich dwa tygodnie temu:

  • Dowiedz Objective-C
  • Napisz native iPhone App
  • uwierzytelnionych programowo z Shibboleth
  • pobrania wyświetl chroniony plik danych Shibboleth

... Był trochę zniechęcający. Złożenie tego, że brak jakichkolwiek postów na forum pomogło mi podzielić się moimi doświadczeniami.

Oto przegląd, po którym następuje miejmy nadzieję, bardzo pomocny przykładowy kod. Głosujcie na moją odpowiedź, jeśli to pomoże! Warto kilka tygodni mojego czasu :)

dla aplikacji na iPhone do pobrania Shibbolized zasobów następujące musi się zdarzyć:

  1. użyć adresu URL API w kakao do złożenia żądania HTTP dla dany zasób.
  2. Wdrożenie klasy delegata na żądanie:
  3. odpowiedzieć na przekierowanie do IdP (automatyczny dzięki uprzejmości kakao) Sp
  4. Odpowiadać do zaufania certyfikatu serwera kwestionuje
  5. Odpowiadać do poświadczeń użytkownika kwestionuje
  6. Reakcja na błędy (jeśli to konieczne)
  7. Otrzymuj szablon „wiązanie” IDP dla uwierzytelnionego użytkownika, formularza HTML, który przekierowuje użytkownika z powrotem do SP z dwoma parametrami
  8. Programowo HTTP POST dwa parametry od IdP z powrotem do SP.
  9. Pliki cookie są automatycznie zapisywane i przesyłane za zgodą Cocoa ponownie.
  10. Implementacja drugiego adresu URL Poproś delegata o otrzymanie oryginalnych danych żądania.

Oto kilka przydatnych referencje od Apple i Shibboleth:

I mam nadzieję, że mogę to wszystko źródła dla szybkiego demonstracji.

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

Przyjemnie, ale nie działa z uwierzytelnianiem opartym na formularzach, gdzie trzeba parsować HTML lub prezentować go użytkownikowi w celu interakcji. W moim przypadku użytkownik musi powtórzyć kilka czynników uwierzytelniających, więc nie mogę się z tym pogodzić. W każdym razie mogę przetłumaczyć twoją próbkę na C#, mój ulubiony język do programowania iPhone'a i użyć go jako podstawy. Dzięki – Monoman

0

Z powodzeniem wdrożyłem rozwiązanie EC jako punkt wyjścia.Jedyną rzeczą, którą chciałbym dodać, jest to, że naprawdę musisz zwracać uwagę na to, aby tylko jedna prośba była wykonywana w danym momencie. W naszej implementacji proces uwierzytelniania zostałby zmylony między wieloma asynchronicznymi żądaniami działającymi jednocześnie. Używanie NSOperation do dławienia kolejki wydawało mi się skuteczne.

0

Udało mi się to zrobić, ale zajęło mi trochę czasu, aby zrozumieć każdy krok procesu i doskonale go odtworzyć. Jeśli mam czas, mogę napisać szczegółowy samouczek, ponieważ nie znalazłem żadnej pomocy dla wielu problemów, które mam. Chodzi o to, że zależy to również od strony, z którą chcesz się połączyć, więc Twój może nie podąża tą samą ścieżką co moja (jej proces jest taki sam, jak ten opisany here).

Aby zobaczyć każde żądanie uruchomione przez moją przeglądarkę (Chrome) w celu połączenia, skorzystałem z narzędzi programistycznych Panel sieci, z zaznaczoną opcją "Zachowaj dziennik".

Kilka podpowiedzi:

  • 1 °) Musisz dostać "_idp_authn_lc_key ..." cookie. Jest prośba, która ustawi to dla ciebie, znajdź to.

  • 2 °) Potrzebujesz biletu do logowania (LT -...). Prawdopodobnie znajdziesz go w treści strony, która prosi o twoje dane uwierzytelniające.

  • 3 °) Potrzebujesz biletu serwisowego (ST -...). Ponownie znajdziesz to na stronie, na którą zwrócono poprzednie żądanie.

  • 4 °) Potrzebujesz SAMLResponse. Ponownie znajdziesz to na stronie, na którą zwrócono poprzednie żądanie.

  • 5 °) Na koniec można się zalogować, odsyłając SAMLResponse do usługodawcy. Powinieneś zająć się kodowaniem tutaj. Miałem kilka "+" lub "=", które musiałem zmienić na "% 2B" i "% 3D". Otrzymasz ciasteczko "_idp_session", które pozwoli ci połączyć się ponownie bez całego tego bałaganu.

Jeśli ktoś spróbuje zrobić to samo, chętnie pomogę! Po prostu wyślij mi wiadomość.