0

我有一個「傳統」企業iPad應用程序,需要使用NTLM身份驗證在其生命週期內進行多種不同的Web服務調用。在啓動應用程序後,我預計從用戶名和密碼中取出一個鑰匙串(應用程序首次使用時保存它的鑰匙串沒有用戶名,隨後在密碼失效時更新,因爲更新)。用於執行NTLM Web服務調用的適當的NSURLConnection/Credential模式是什麼?

在啓動時,需要各種Web服務調用來獲取應用程序的初始數據。然後,用戶將看到一個選項卡式控制器,以選擇他們想要的功能,這反過來將會進行更多的Web服務調用。

我相信我有一個策略來處理每個類通過自定義數據代理接收數據,如在這個StackOverflow答案(How do you return from an asynchronous NSURLConnection to the calling class?)中提供的。不過,我仍然對如何正確使用-(void)useCredential:(NSURLCredential *)credential forAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge功能感到困惑。

在didReceiveAuthenticationChallenge,我有這樣的

[[challenge sender] useCredential:[NSURLCredential credentialWithUser:@"myusername" 
          password:@"mypassword" 
         persistence:NSURLCredentialPersistencePermanent] 
     forAuthenticationChallenge:challenge]; 

代碼,因爲我設置永久持續,我預計不會有不斷在功能的用戶名和密碼通過。是否有一種模式用於初始設置用戶的NTLM證書(和/或檢查它們是否已經存在),然後僅對後續Web服務調用使用「永久」證書?

此外,作爲第二個問題/部分對此。在整個Objective-C應用程序中傳遞用戶名/密碼的適當/優雅的方法是什麼?我在想全球變種或單身實例(這似乎有點矯枉過正只是幾個需要的變種)。

回答

0

我們已經解決了這個問題併成功地解決了這個問題。我認爲是時候在這裏提出答案。下面的代碼屬於它自己的類,並不能很好地工作,但應該讓你有更長的路要走。大多數情況下,這一切都應該可以正常工作,但只需確保警報視圖,數據存儲等各個方面都按照您的需要進行設置。

我們理解Objective-C & iOS處理NTLM通信的一個主要絆腳石是找出它與URL通信的正常過程。

與URL的第一次聯繫是匿名完成的。當然,在Windows安全的環境中,這將會失敗。這是應用程序將嘗試再次聯繫URL的時間,但是這次使用的密鑰鏈中已有該URL的任何憑證,並使用willSendRequestForAuthenticationChallenge方法。這對我們來說非常混亂,因爲這種方法在第一次通話失敗之後纔會觸發。它終於明白了我們第一次匿名的情況。

你會在這裏看到的模式的一部分是連接將嘗試鑰匙串上已有的任何憑證。如果這些失敗/缺失,我們將彈出一個視圖,要求用戶輸入用戶名和密碼,然後重試。

我們需要考慮一些特質,你會在整個代碼中看到這些特質。它需要很多迭代和大量測試才能保持穩定。其中很大一部分是基於遍佈互聯網的模式,因爲它們完成了我們正在嘗試做的事情,但並沒有完全把我們帶到那裏。

我們所做的代碼概括了GET/POST調用。這是我第一次發佈到StackOverflow的主要代碼帖子,如果我錯過了一些約定,我會更正我在引起我注意時需要更正的內容。

#import "MYDataFeeder.h" 
#import "MYAppDelegate.h" 
#import "MYDataStore.h" 
#import "MYAuthenticationAlertView.h" 
#import "MYExtensions.h" 

@interface MYDataFeeder() <NSURLConnectionDelegate> 
    @property (strong, nonatomic) void (^needAuthBlock)(NSString *, NSString *); 
    @property (strong, nonatomic) void (^successBlock)(NSData *); 
    @property (strong, nonatomic) void (^errorBlock)(NSError *); 
@end 


@implementation MYDataFeeder{ 
    NSMutableData *_responseData; 
    NSString *_userName; 
    NSString *_password; 
    NSString *_urlPath; 
    BOOL _hasQueryString; 
} 

+ (void)get: (NSString *)requestString 
    userName: (NSString *)userName 
    password: (NSString *)password 
hasNewCredentials: (BOOL)hasNewCredentials 
successBlock: (void (^)(NSData *))successBlock 
errorBlock: (void (^)(NSError *))errorBlock 
needAuthBlock: (void (^)(NSString *, NSString *))needAuthBlock 
{ 
    MYDataFeeder *x = [[MYDataFeeder alloc] initWithGetRequest:requestString userName:userName password:password hasNewCredentials:hasNewCredentials successBlock:successBlock errorBlock:errorBlock needAuthBlock:needAuthBlock]; 
} 

+ (void)post: (NSString *)requestString 
    userName: (NSString *)userName 
    password: (NSString *)password 
hasNewCredentials: (BOOL)hasNewCredentials 
    jsonString: (NSString *)jsonString 
successBlock: (void (^)(NSData *))successBlock 
    errorBlock: (void (^)(NSError *))errorBlock 
needAuthBlock: (void (^)(NSString *, NSString *))needAuthBlock 
{ 
    MYDataFeeder *x = [[MYDataFeeder alloc] initWithPostRequest:requestString userName:userName password:password hasNewCredentials:hasNewCredentials jsonString:jsonString successBlock:successBlock errorBlock:errorBlock needAuthBlock:needAuthBlock]; 
} 

- (instancetype)initWithGetRequest: (NSString *)requestString 
          userName: (NSString *)userName 
          password: (NSString *)password 
       hasNewCredentials: (BOOL)hasNewCredentials 
         successBlock: (void (^)(NSData *))successBlock 
         errorBlock: (void (^)(NSError *))errorBlock 
        needAuthBlock: (void (^)(NSString *, NSString *))needAuthBlock 
{ 
    return [self initWithRequest:requestString userName:userName password:password hasNewCredentials:hasNewCredentials isPost:NO jsonString:nil successBlock:successBlock errorBlock:errorBlock needAuthBlock:needAuthBlock]; 
} 

-(instancetype)initWithPostRequest: (NSString *)requestString 
          userName: (NSString *)userName 
          password: (NSString *)password 
       hasNewCredentials: (BOOL)hasNewCredentials 
         jsonString: (NSString *)jsonString 
         successBlock: (void (^)(NSData *))successBlock 
         errorBlock: (void (^)(NSError *))errorBlock 
        needAuthBlock: (void (^)(NSString *, NSString *))needAuthBlock 
{ 
    return [self initWithRequest:requestString userName:userName password:password hasNewCredentials:hasNewCredentials isPost:YES jsonString:jsonString successBlock:successBlock errorBlock:errorBlock needAuthBlock:needAuthBlock]; 
} 

//Used for NTLM authentication when user/pwd needs updating 
- (instancetype)initWithRequest: (NSString *)requestString 
         userName: (NSString *)userName 
         password: (NSString *)password 
       hasNewCredentials: (BOOL)hasNewCredentials 
         isPost: (BOOL)isPost 
         jsonString: (NSString *)jsonString 
        successBlock: (void (^)(NSData *))successBlock 
        errorBlock: (void (^)(NSError *))errorBlock 
        needAuthBlock: (void (^)(NSString *, NSString *))needAuthBlock //delegate:(id<MYDataFeederDelegate>)delegate 
{ 
    self = [super init]; 

    requestString = [requestString stringByAddingPercentEscapesUsingEncoding:NSASCIIStringEncoding]; 

    if(self) { 
     if (!errorBlock || !successBlock || !needAuthBlock) { 
      [NSException raise:@"MYDataFeeder Error" format:@"Missing one or more execution blocks. Need Success, Error, and NeedAuth blocks."]; 
     } 

     _responseData = [NSMutableData new]; 
     _userName = userName; 
     _password = password; 
     _successBlock = successBlock; 
     _hasNewCredentials = hasNewCredentials; 
     _errorBlock = errorBlock; 
     _needAuthBlock = needAuthBlock; 
     NSString *host = [MYDataStore sharedStore].host; //Get the host string 
     int port = [MYDataStore sharedStore].port; //Get the port value 
     NSString *portString = @""; 

     if (port > 0) { 
      portString = [NSString stringWithFormat:@":%i", port]; 
     } 

     requestString = [NSString stringWithFormat:@"%@%@/%@", host, portString, requestString]; 
     NSURL *url = [NSURL URLWithString:requestString]; 

     NSString *absoluteURLPath = [url absoluteString]; 
     NSUInteger queryLength = [[url query] length]; 
     _hasQueryString = queryLength > 0; 
     _urlPath = (queryLength ? [absoluteURLPath substringToIndex:[absoluteURLPath length] - (queryLength + 1)] : absoluteURLPath); 

     NSTimeInterval timeInterval = 60; //seconds (60 default) 

     NSMutableURLRequest *request; 

     if (isPost) { 
      request = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:timeInterval]; 

      NSData *requestData = [jsonString dataUsingEncoding:NSUTF8StringEncoding]; 

      [request setHTTPMethod:@"POST"]; 
      [request setValue:@"application/json" forHTTPHeaderField:@"Accept"]; 
      [request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"]; 
      [request setValue:[NSString stringWithFormat:@"%lu", (unsigned long)requestData.length] forHTTPHeaderField:@"Content-Length"]; 
      [request setHTTPBody: requestData]; 
      [request setHTTPShouldHandleCookies:YES]; 
     } 
     else { 
      request = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:timeInterval]; 
     } 

     NSURLConnection* connection = [[NSURLConnection alloc] initWithRequest:request delegate:self]; 
    } 

    return self; 
} 

- (instancetype)initWithRequest: (NSString *)requestString 
        successBlock: (void (^)(NSData *))successBlock 
        errorBlock: (void (^)(NSError *))errorBlock 
        needAuthBlock: (void (^)(NSString *, NSString *))needAuthBlock //delegate:(id<MYDataFeederDelegate>)delegate 
{ 
    return [self initWithRequest:requestString userName:NULL password:NULL hasNewCredentials:NO isPost:NO jsonString:nil successBlock:successBlock errorBlock:errorBlock needAuthBlock:needAuthBlock]; //delegate:delegate]; 
} 

#pragma mark - Connection Events 

- (BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace { 
    return YES; 
} 

- (BOOL)connectionShouldUseCredentialStorage:(NSURLConnection *)connection { 
    return YES; 
} 

- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response 
{ 
    if (response){ 
     NSHTTPURLResponse* httpResponse = (NSHTTPURLResponse*)response; 
     NSInteger code = httpResponse.statusCode; 

     if (code == 401){ 
      NSLog(@"received 401 response"); 
      [MYAuthenticationAlertView showWithCallback:_needAuthBlock]; 
      [connection cancel]; 
     } 
    } 
} 

- (void)connectionDidFinishLoading:(NSURLConnection *)connection { 
    _successBlock(_responseData); 
} 

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


- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error 
{ 
    _errorBlock(error); 
} 

- (void) connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge 
{ 
    if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodNTLM]) 
    { 
     BOOL hasConnectionCredentials = [[MYDataStore sharedStore] hasConnectionCredentials]; //Determines if there's already credentials existing (see method stub below) 
     long previousFailureCount = [challenge previousFailureCount]; 

     BOOL hasFailedAuth = NO; 

     //If the application has already gotten credentials at least once, then see if there's a response failure... 
     if (hasConnectionCredentials){ 
      //Determine if this URL (sans querystring) has already been called; if not, then assume the URL can be called, otherwise there's probably an error... 
      if ([[MYDataStore sharedStore] isURLUsed:_urlPath addURL:YES] && !_hasQueryString){ 
       NSURLResponse *failureResponse = [challenge failureResponse]; 

       if (failureResponse){ 
        NSHTTPURLResponse* httpResponse = (NSHTTPURLResponse*)[challenge failureResponse]; 
        long code = [httpResponse statusCode]; 

        if (code == 401){ 
         hasFailedAuth = YES; 
        } 
       } 
      } 
     } 
     else{ 
      //Need to get user's credentials for authentication... 
      NSLog(@"Does not have proper Credentials; possible auto-retry with proper protection space."); 
     } 

     /* This is very, very important to check. Depending on how your security policies are setup, you could lock your user out of his or her account by trying to use the wrong credentials too many times in a row. */ 
     if (!_hasNewCredentials && ((previousFailureCount > 0) || hasFailedAuth)) 
     { 
      NSLog(@"prompt for new creds"); 
      NSLog(@"Previous Failure Count: %li", previousFailureCount); 
      [[challenge sender] cancelAuthenticationChallenge:challenge]; 
      [MYAuthenticationAlertView showWithCallback:_needAuthBlock]; 
      [connection cancel]; 
     } 
     else 
     { 
      if (_hasNewCredentials){ 
       //If there's new credential information and failures, then request new credentials again... 
       if (previousFailureCount > 0) { 
        NSLog(@"new creds failed"); 
        [MYAuthenticationAlertView showWithCallback:_needAuthBlock]; 
        [connection cancel]; 
       } else { 
        NSLog(@"use new creds"); 
        //If there's new credential information and no failures, then pass them through... 
        [[challenge sender] useCredential:[NSURLCredential credentialWithUser:_userName password:_password persistence:NSURLCredentialPersistencePermanent] forAuthenticationChallenge:challenge]; 
       } 
      } else { 
       NSLog(@"use stored creds"); 
       //...otherwise, use any stored credentials to call URL... 
       [[challenge sender] performDefaultHandlingForAuthenticationChallenge:challenge]; 
      } 
     } 
    } 
    else if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) { // server trust challenge 
     // make sure challenge came from environment host 
     if ([[MYDataStore sharedStore].host containsString:challenge.protectionSpace.host]) { 
      [challenge.sender useCredential:[NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust] forAuthenticationChallenge:challenge]; 
     } 
     [challenge.sender continueWithoutCredentialForAuthenticationChallenge:challenge]; 
    } 
    else { 
     // request has failed 
     [[challenge sender] cancelAuthenticationChallenge:challenge]; 
    } 
} 

@end 

-(BOOL) hasConnectionCredentials 
{ 
    NSDictionary *credentialsDict = [[NSURLCredentialStorage sharedCredentialStorage] allCredentials]; 
    return ([credentialsDict count] > 0); 
} 

//Sample use of Data Feeder and blocks: 
-(void)myMethodToGetDataWithUserName:(NSString*)userName password:(NSString*)password{ 
//do stuff here 
[MYDataFeeder get:@"myURL" 
userName:userName 
password:password 
hasNewCredentials:(userName != nil) 
successBlock:^(NSData *response){ [self processResponse:response]; } 
      errorBlock:^(NSError *error) { NSLog(@"URL Error: %@", error); } 
     needAuthBlock:^(NSString *userName, NSString *password) { [self myMethodToGetDataWithUserName:username withPassword:password]; } 
]; 
} 

//The needAuthBlock recalls the same method but now passing in user name and password that was queried from within an AlertView called from within the original DataFeeder call 
+0

我可以得到完整的文件! – dip

+0

抱歉,我以前沒有看到您的請求!我試圖給出一個相當全面的例子,以這種方式來回避給出確切的公司(以及潛在的「敏感」)文件。我會嘗試將一組「工作」的文件放在一起,但這可能需要一些時間,因爲我現在對其他工作非常分心(我謙卑的道歉)。 – Prethen

相關問題