【问题标题】:sendAsynchronousRequest:queue:completionHandler: With Delegate MethodssendAsynchronousRequest:queue:completionHandler: 使用委托方法
【发布时间】:2014-07-23 00:30:12
【问题描述】:

我有一台安装了自签名 SSL 证书的服务器。但是,一旦我调用以下方法,它就不会得到任何响应。一旦我将 URL 改回 http,它就可以工作了。

- (void)getAccountInfoWithCompletion:(void (^)(NSDictionary *json_response, NSError *error))completion
{
    NSURLRequest *request = [NSURLRequest requestWithURL:
                             [NSURL URLWithString:
                              [NSString stringWithFormat:@"%@/api/account/%d/get_info", BASE_HOST_URL_IP, [self getUserID]]
                            ]];

    [NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
        if (error)
        {
            if (completion)
            {
                //completion(@"error", error);
            }
        } else {
            NSString *response_string = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
            NSDictionary *json_object = [NSJSONSerialization JSONObjectWithData:[response_string dataUsingEncoding:NSUTF8StringEncoding] options:0 error:nil];

            if (completion)
            {
                completion(json_object, error);
            }
        }
    }];
}

我选择委托的原因是我可以在我的应用程序中使用自签名证书。以下教程是我正在使用的,但后来我意识到我不能将委托与 completionHandler 方法一起使用。不过我需要保留 completionHandler 方法。

http://www.cocoanetics.com/2010/12/nsurlconnection-with-self-signed-certificates/

如何才能收到来自 SSL 站点的响应?

【问题讨论】:

  • 旁注 - 不要检查 error 以了解是否有错误。检查data 是否为nil。如果是nil,则有错误。
  • 要完成这项工作,您不能使用这种方便的方法。使用“常规”NSURLConnection 并实现所有需要的委托方法。
  • @rmaddy 绝对没有办法?
  • 您需要使用NSURLSession 等效/替换,这将为您提供一个委托方法来处理 SSL 验证。我最近不得不做同样的事情:)
  • @troop231 那么 NSURLSession 会像 completionHandler 方法吗?你能指点我那个方向吗?

标签: ios objective-c cocoa-touch nsurlconnection


【解决方案1】:

在您描述的情况下,您(几乎)必须使用委托。

这里发生的是 sendAsynchronousRequest:queue:completion: 使用 URL 加载系统的默认行为。 URL 加载系统看到您的自签名证书,无法验证它,因此它无法信任它 - 并且不会连接。您应该会看到传递给完成处理程序的 NSError 填充了有关问题的信息。

这一切都在Technote 2232: HTTPS Server Trust Evaluation中进行了深入描述

要允许您的自签名证书,您不能使用sendAsynchronousRequest:queue:completion:,除非您有办法使您的自签名证书受信任并存储在钥匙串中 - 在 iOS 上,这仅在托管设备中实用。对于测试,并且仅用于测试,您可以使用 private Apple API 来更改默认信任行为。

对于生产代码,您必须实现一个 NSURLConnectionDelegate 来处理评估服务器提供的凭据并允许您的自签名证书。这在 Technote 2232 中也有描述(在高层次上)。如果您没有正确实现这一点,您可能会在您的应用程序中创建一个安全漏洞 - 这会很糟糕,mmmmmk?

我不建议遵循您参考的 Cocoanetics 帖子的指导。该材料已过时且质量有问题。请参阅NSURLConnectionDelegate 的文档和提到的Technote 2232。如果您想了解有关移动应用程序传输级别安全性的更多信息,请访问 plenty of resources

如果您仍想使用自签名证书,您可以实施 SSL 公钥固定,以将远程(自签名)公钥与存储在应用程序中的已知本地值进行匹配。这比尝试仅匹配主机名要好得多。一些帮助您入门的示例代码是here

【讨论】:

    【解决方案2】:

    ViewController.h:

    @interface ViewController : UIViewController <NSURLSessionDelegate>
    
    @end
    

    ViewController.m:

    - (void)getAccountInfoWithCompletion:(void (^)(NSDictionary *json_response, NSError *error))completion
    {
        NSURLRequest *request = [NSURLRequest requestWithURL:
                                 [NSURL URLWithString:
                                  [NSString stringWithFormat:@"%@/api/account/%d/get_info", BASE_HOST_URL_IP, [self getUserID]]
                                  ]];
    
        NSURLSessionConfiguration *defaultConfigObject = [NSURLSessionConfiguration ephemeralSessionConfiguration];
        defaultConfigObject.requestCachePolicy = NSURLRequestReloadIgnoringLocalAndRemoteCacheData;
    
        NSURLSession *defaultSession = [NSURLSession sessionWithConfiguration:defaultConfigObject delegate:self delegateQueue: [NSOperationQueue mainQueue]];
    
        [UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
    
        NSURLSessionDataTask *dataTask = [defaultSession dataTaskWithRequest:request
                                                           completionHandler:^(NSData *data, NSURLResponse *response, NSError *error)
        {
            [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
    
            if (error == nil && data != nil)
            {
                NSString *response_string = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
                NSDictionary *json_object = [NSJSONSerialization JSONObjectWithData:[response_string dataUsingEncoding:NSUTF8StringEncoding] options:0 error:nil];
    
                if (completion)
                {
                    completion(json_object, error);
                }
            }
        }];
        [dataTask resume];
    }
    

    新的漂亮委托方法让我们替换 NSURLConnection 的 sendAsynchronousRequest 方法(无法处理 SSL)

    - (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential *))completionHandler
    {
        NSString *host = challenge.protectionSpace.host;
    
        if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust])
        {
            if ([host rangeOfString:@"yourHost.net"].location != NSNotFound)
            {
                completionHandler(NSURLSessionAuthChallengeUseCredential, [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]);
            }
            else
            {
                completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge,nil);
            }
        }
    }
    

    【讨论】:

    • 您正在检查服务器提供的凭据是否包含预期的字符串。不幸的是,这使您面临许多易于执行的中间人攻击:(
    • 我不确定您所说的“仍然是 SSL”是什么意思。委托负责确定服务器的信任。您信任任何提供包含字符串“yourHost.net”的凭据的服务器。那是极其危险的。从 iOS 5 及更高版本开始,willSendRequestForAuthenticationChallenge: 是自定义身份验证的首选位置。文档对此非常清楚。
    • 你能发布一些关于如何快速修复它的代码吗?您是否必须解密传入的证书或其他东西?
    • 查看我的答案中的链接。您应该实现“SSL Pinning”,将公钥的本地副本与服务器提供的公钥进行比较。没有“快速”修复,但我可以展示如何做到这一点。不过,它可能应该在一个单独的问题中。
    • 好吧,很酷,我必须输入一个问题,因为我在我的应用程序中做错了。 :(现在要吐了
    猜你喜欢
    • 2012-04-29
    • 1970-01-01
    • 2016-11-10
    • 1970-01-01
    • 1970-01-01
    • 2011-07-23
    • 1970-01-01
    • 2011-03-03
    • 1970-01-01
    相关资源
    最近更新 更多