Objective c 进行NTLM web服务调用的合适NSURLConnection/Credential模式是什么?

Objective c 进行NTLM web服务调用的合适NSURLConnection/Credential模式是什么?,objective-c,nsurlconnection,credentials,ntlm,nsurlcredential,Objective C,Nsurlconnection,Credentials,Ntlm,Nsurlcredential,我有一个传统的企业iPad应用程序,它需要在其生命周期内使用NTLM身份验证进行许多不同的web服务调用。在启动应用程序时,我预计会从应用程序第一次使用时保存的钥匙链中获取用户名和密码,因为钥匙链没有用户名,并且在密码由于更新而无法工作时更新 启动时,需要各种web服务调用来获取应用程序的初始数据。然后,用户将看到一个选项卡式控制器,以选择他们想要的功能,当然,这些功能将执行更多的web服务调用 我相信我有一个策略来处理通过自定义数据委托接收数据的每个类,如本答案所示。但是,对于如何正确使用-v

我有一个传统的企业iPad应用程序,它需要在其生命周期内使用NTLM身份验证进行许多不同的web服务调用。在启动应用程序时,我预计会从应用程序第一次使用时保存的钥匙链中获取用户名和密码,因为钥匙链没有用户名,并且在密码由于更新而无法工作时更新

启动时,需要各种web服务调用来获取应用程序的初始数据。然后,用户将看到一个选项卡式控制器,以选择他们想要的功能,当然,这些功能将执行更多的web服务调用

我相信我有一个策略来处理通过自定义数据委托接收数据的每个类,如本答案所示。但是,对于如何正确使用-voiduseCredential:NSURLCredential*credential for AuthenticationChallenge:NSURLAuthenticationChallenge*质询功能,我仍然有点困惑

在didReceiveAuthenticationChallenge中,我有这样的代码

[[challenge sender]  useCredential:[NSURLCredential credentialWithUser:@"myusername"
                          password:@"mypassword"
                       persistence:NSURLCredentialPersistencePermanent]
        forAuthenticationChallenge:challenge];
由于我正在设置永久持久性,我希望不必在功能中不断地传递用户名和密码。是否有一种模式用于最初设置用户的NTLM凭据和/或检查它们是否已经存在,然后仅使用永久凭据进行后续web服务调用


此外,作为第二个问题/部分。在Objective-C应用程序中传递用户名/密码的合适/优雅方法是什么?我想要么是全局var,要么是单个实例,这对于几个需要的var来说似乎有点过头了。

我们已经解决了这个问题并成功地解决了它,这已经有一段时间了。我想是时候在这里给出答案了。下面的代码属于它自己的类,不会完全开箱即用,但应该可以帮助您实现所需的目标。在大多数情况下,这一切都应该可以正常工作,但您只需要确保各个区域(如警报视图、数据存储等)都按照您需要的方式进行设置

在我们理解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

我可以得到完整的文件吗!对不起,我之前没有看到你的请求!我试图给出一个相当全面的例子,就像我所做的那样,避免给出确切的公司和潜在的敏感文件。我将尝试整理一组工作文件,但这可能需要一些时间,因为目前我正忙于其他工作,我谨此致歉。