将 iPhone 应用程序与 Shibboleth 集成
Posted
技术标签:
【中文标题】将 iPhone 应用程序与 Shibboleth 集成【英文标题】:Integrating iPhone Application with Shibboleth 【发布时间】:2010-12-28 10:42:44 【问题描述】:是否有人将 iPhone 应用程序与 Shibboleth 身份提供程序集成?谷歌搜索没有想出任何东西,所以我直接问大师。
如果之前没有做过,是否可行?
【问题讨论】:
你说的是网络应用还是原生应用? 本机应用程序;还是 iPhone 网页可以验证本地应用程序? 【参考方案1】:两者的答案都是“是”。
我是一名 Java 人,所以两周前有人问我:
学习 Objective-C 编写原生 iPhone 应用程序 使用 Shibboleth 以编程方式进行身份验证 下载受 Shibboleth 保护的显示数据文件...有点吓人。由于没有任何论坛帖子可以提供帮助,这促使我分享我的经验。
这里是一个概述,然后是一些很有帮助的示例代码。如果这有帮助,请投票给我的答案!这值得我花几个星期的时间:)
要让 iPhone 上的应用程序下载 Shibbolized 资源,需要进行以下操作:
-
使用 Cocoa 中的 URL API 提交相关资源的 HTTP 请求。
为请求实现一个委托类:
响应 SP 重定向到 IdP(由 Cocoa 自动提供)
响应服务器证书信任挑战
响应用户凭据挑战
响应错误(如果需要)
为经过身份验证的用户接收 IdP 的“绑定模板”,这是一个 html 表单,它使用两个参数将用户重定向回 SP
以编程方式将两个参数从 IdP 发送回 SP。
Cookie 会自动存储并再次由 Cocoa 转发
实现第二个 URL 请求委托以接收原始请求数据。
以下是来自 Apple 和 Shibboleth 的一些有用参考:
http://developer.apple.com/mac/library/DOCUMENTATION/Cocoa/Conceptual/URLLoadingSystem/URLLoadingSystem.html https://spaces.internet2.edu/display/SHIB2/IdPSPLocalTestInstall希望我可以包含所有源代码以进行快速演示。
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
【讨论】:
很好,但它不适用于基于表单的身份验证,您需要在哪里解析 html 或将其呈现给用户进行交互。在我的情况下,用户需要遍历多个身份验证因素,所以我无法克服它。无论如何,我可以将您的示例翻译成 C#,这是我用于 iPhone 开发的首选语言,并将其用作基础。谢谢【参考方案2】:我设法做到了这一点,但我花了一些时间来理解这个过程的每一步并完美地重现它。如果我有时间,我可能会写一个详细的教程,因为我遇到的很多问题都没有找到任何帮助。问题是,它还取决于您要连接的网站,因此您的可能与我的路径不同(其过程与here描述的过程相同)。
为了查看我的浏览器 (Chrome) 触发的每个连接请求,我使用了开发人员工具的网络面板,并选中了“保留日志”。
一些提示:
1°) 您需要获取“_idp_authn_lc_key...”cookie。有一个为你设置的请求,找到它。
2°) 您需要登录票证 (LT-...)。您可能会在询问您的凭据的页面正文中找到它。
3°) 您需要一张服务票 (ST-...)。同样,您会在上一个请求返回的页面中找到它。
4°) 您需要 SAMLResponse。同样,您会在上一个请求返回的页面中找到它。
5°) 最后,您可以通过向服务提供商发送回 SAMLResponse 来登录。您应该在这里处理编码。我有几个 '+' 或 '=' 需要更改为 '%2B' 和 '%3D'。您将获得一个“_idp_session”cookie,这将允许您重新连接而不会出现所有这些混乱。
如果有人尝试这样做,我很乐意提供帮助!请给我发消息。
【讨论】:
【参考方案3】:我以 EC 的解决方案为起点成功实施。我要补充的唯一另一件事是,您确实必须注意一次只保留一个请求。在我们的实现中,身份验证过程会在同时运行的多个异步请求之间混淆。使用 NSOperation 来限制队列似乎对我很有用。
【讨论】:
以上是关于将 iPhone 应用程序与 Shibboleth 集成的主要内容,如果未能解决你的问题,请参考以下文章