将iPhone应用程序与Shibboleth集成

时间:2009-12-20 06:05:54

标签: iphone shibboleth

有没有人将iPhone应用程序与Shibboleth身份提供程序集成?谷歌搜索没有得到任何东西,所以我直接问大师。

如果之前没有用过,这样做是否可行?

3 个答案:

答案 0 :(得分:16)

两者的答案都是“是的。”

我是一个Java人,因此在两周前被问到:

  • 学习Objective-C
  • 编写原生iPhone应用程序
  • 使用Shibboleth以编程方式进行身份验证
  • 下载显示Shibboleth protected datafile

......有点令人生畏。在没有任何论坛帖子的情况下提供帮助的复合物促使我分享我的经验。

这是一个概述,然后是一些希望非常有用的示例代码。如果这有帮助,请投票给我答案!值得我几个星期的时间:))

要在iPhone上下载Shibbolized资源的应用程序,需要执行以下操作:

  1. 使用Cocoa中的URL API提交相关资源的HTTP请求。
  2. 为请求实现委托类:
  3. 回应SP重新指向IdP(由Cocoa自动提供)
  4. 响应服务器证书信任质询
  5. 回应用户凭据质询
  6. 回应错误(如果需要)
  7. 为经过身份验证的用户接收IdP的“绑定模板”,这是一个HTML表单,用两个参数将用户重定向回SP
  8. 以编程方式HTTP将两个参数从IdP发送回SP。
  9. Cookie会自动存储并再次由Cocoa提供
  10. 实施第二个URL请求委托以接收最初的请求数据。
  11. 以下是Apple和Shibboleth的一些有用的参考资料:

    希望我可以包含所有来源以进行快速演示。

    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
    

答案 1 :(得分:1)

我设法做到了这一点,但我花了一些时间来理解这个过程的每一步并完美地再现它。如果我有时间,我可能会写一篇详细的教程,因为我找不到任何有关我遇到的问题的帮助。问题是,它还取决于您要连接的网站,因此您的网站可能不会遵循与我相同的路径(其过程与描述here的过程相同。)

要查看我的浏览器(Chrome)发出的每个请求进行连接,我使用了开发人员工具“网络”面板,并选中了“保留日志”。

一些提示:

  • 1°)您需要获取“_idp_authn_lc_key ...”cookie。有一个请求为您设置,找到它。

  • 2°)您需要登录票证(LT -...)。您可能会在页面正文中找到它,询问您的凭据。

  • 3°)您需要一张服务票(ST -...)。同样,您将在页面中找到上一个请求返回的内容。

  • 4°)您需要SAMLResponse。同样,您将在页面中找到上一个请求返回的内容。

  • 5°)最后,您可以通过向服务提供商发送回SAMLResponse来登录。你应该在这里处理编码。我有一些'+'或'='我需要更改为'%2B'和'%3D'。您将获得一个“_idp_session”cookie,这将允许您在没有所有这些混乱的情况下重新连接。

如果有人试图这样做,我很乐意提供帮助!请给我发消息。

答案 2 :(得分:0)

我成功地使用EC的解决方案作为起点。我要补充的另一件事是你真的要注意一次只保留一个请求。在我们的实现中,身份验证过程会在同时运行的多个异步请求之间混淆。使用NSOperation来限制队列对我来说似乎很有用。