美文网首页
iOS NSURLAuthenticationChallenge

iOS NSURLAuthenticationChallenge

作者: 天下林子 | 来源:发表于2018-09-27 16:45 被阅读59次

    NSURLAuthenticationChallenge

    在http请求中某些url访问需要具有权限认证,否则会返回401错误码,这时需要你在请求头中附带授权的用户名,密码;或者使用https协议第一次与服务端建立连接的时候,服务端会发送iOS客户端一个证书,这时我们需要验证服务端证书链(certificate keychain)。
    当我们遇到以上情况的时候NSURLSession(在iOS7以后网络请求全部由NSURLSession来完成所以在此以NSURLSession为例)中的一个delegate会被调用:

    
    - (void)URLSession:(NSURLSession *)session
                  task:(NSURLSessionTask *)task
    didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
     completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
     
    

    这个代理就是为了处理从服务端传回的NSURLAuthenticationChallenge(认证),在NSURLAuthenticationChallenge的protectionSpace中存放了服务端返回的认证信息,我们需要根据这些信息来创建对应的NSURLCredential(证书/凭证),并且设置对应的NSURLSessionAuthChallengeDisposition(处理方式)来告诉NSURLSession来如何处理处服务端返回的个认证.
    在NSURLSessionAuthChallengeDisposition中包含三种类型

    NSURLSessionAuthChallengePerformDefaultHandling:默认方式处理
     NSURLSessionAuthChallengeUseCredential:使用指定的证书
     NSURLSessionAuthChallengeCancelAuthenticationChallenge:取消
    
    

    AFNetworking在代理中做了如下处理:

    NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
        __block NSURLCredential *credential = nil;
    
        if (self.sessionDidReceiveAuthenticationChallenge)
        {   // 自定义认证处理方式
            disposition = self.sessionDidReceiveAuthenticationChallenge(session, challenge, &credential);
        }
        else
        {   // 默认处理方式
            if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust])
            {
                // 根据AFSecurityPolicy的默认规则判断是否需要新建证书
                if ([self.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host])
                {
                    credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]; // 根据服务器返回的challenge中的protectionSpace中的SecTrustRefwww创建证书
                    if (credential) {
                        disposition = NSURLSessionAuthChallengeUseCredential; // 使用创建的证书
                    } else {
                        disposition = NSURLSessionAuthChallengePerformDefaultHandling; //使用默认方式
                    }
                }
                else
                {
                    disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
                }
            }
            else
            {
                disposition = NSURLSessionAuthChallengePerformDefaultHandling;
            }
        }
    
        if (completionHandler) {
            completionHandler(disposition, credential);
        }
    
    
    

    NSURLAuthenticationChallenge 是由服务端返回的权限认证类,中间包含如下信息:

    
    - (NSURLProtectionSpace *)protectionSpace; // 这个函数返回一个类NSURLProtectionSpace,类中描述服务器中希望的认证方式以及协议,主机端口号等信息。
    - (NSURLCredential *)proposedCredential; // 建议使用的证书
    - (NSInteger)previousFailureCount; // 用户密码输入失败的次数。
    - (NSURLResponse *)failureResponse; // 授权失败的响应头的详细信息
    - (NSError *)error; // 最后一次授权失败的错误信息
    
    
    
    image.png

    当客户端发出一个网络请求的时候(图中的第1,2步),服务端会收到来自客户端的请求并作出响应(图中的第3步),这里服务端发现此请求需要输入用户名才能继续,所以客户端返回了一个权限认证,这是客户端中的NSURLSession的delegate会被调用,这时我们可以从challenge中的protectionSpace里的authenticationMethod属性获取到当前服务器需要我们提供什么类型的认证 (在图中authenticationMethod为NSURLAuthenticationMethodHTTPDigest) ,根据不同的类型使用不同的方式创建需要处理的credential然后设置对应的disposition参数,然后通过completionHandler告诉Session如何处理此次的权限认证(其实最后最后session会将此凭证返回给服务端,再由服务端去校验输入用户名密码是否正确,如果正确便继续请求,如果不正确的话会继续返回认证).

    示例代码

    
    - (IBAction)URLAuthenticationChallenge:(id)sender {
        
        NSURL *url = [NSURL URLWithString:@"https://kyfw.12306.cn/otn/leftTicket/init"];
        NSURLRequest *request = [NSURLRequest requestWithURL:url];
        NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[NSOperationQueue mainQueue]];
        NSURLSessionDataTask *task = [session dataTaskWithRequest:request];
        [task resume];
        
    }
    
    
    
    #pragma mark - NSURLSessionDataDelegate
    
    // 只要访问的是HTTPS的路径就会调用
    // 该方法的作用就是处理服务器返回的证书, 需要在该方法中告诉系统是否需要安装服务器返回的证书
    // NSURLAuthenticationChallenge : 授权质问
    //+ 受保护空间
    //+ 服务器返回的证书类型
    
    - (void)URLSession:(NSURLSession *)session
    didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
     completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
    {
        NSLog(@"++++++++++didReceiveChallenge");
        NSLog(@"____authenticationMethod______%@", challenge.protectionSpace.authenticationMethod);
        
        // 1.从服务器返回的受保护空间中拿到证书的类型
        // 2.判断服务器返回的证书是否是服务器信任的
        
        //AFNetworking中的处理方式
        NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
        __block NSURLCredential *credential = nil;
        //判断服务器返回的证书是否是服务器信任的
        if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
            NSLog(@"------->>>>>>是服务器信任的证书");
            // 3.根据服务器返回的受保护空间创建一个证书
            //void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential *)
            //代理方法的completionHandler block接收两个参数:
            //第一个参数: 代表如何处理证书
            //第二个参数: 代表需要处理哪个证书
            
            //创建证书
            credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
            
            /*
             disposition:如何处理证书
             NSURLSessionAuthChallengePerformDefaultHandling:默认方式处理
             NSURLSessionAuthChallengeUseCredential:使用指定的证书    NSURLSessionAuthChallengeCancelAuthenticationChallenge:取消请求
             */
            
            if (credential) {
                disposition = NSURLSessionAuthChallengeUseCredential;
            } else {
                disposition = NSURLSessionAuthChallengePerformDefaultHandling;
            }
        } else {
            disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
        }
        
        //安装证书
        if (completionHandler) {
            completionHandler(disposition, credential);
        }
    }
    
    // 接收到服务器的响应
    - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler
    {
        NSLog(@"+++++++++++didReceiveResponse");
        completionHandler(NSURLSessionResponseAllow);
    }
    
    // 接收到服务器返回的数据
    - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data
    {
        
        //NSString * str =  [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
        //NSData *receiveData = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
        //NSString *str = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
    
        NSLog(@"----------->>>>>didReceiveData--%lu-", (unsigned long)data.length);
        
    }
    
    // 请求完毕
    - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
    {
        NSLog(@"++++++++++++++didCompleteWithError=---%@", error);
    }
    
    
    

    打印结果如下:

    
    2018-09-27 16:35:40.062008+0800 MOBFrameWorkTest[51074:1969808] ++++++++++didReceiveChallenge
    2018-09-27 16:35:40.062133+0800 MOBFrameWorkTest[51074:1969808] ____authenticationMethod______NSURLAuthenticationMethodServerTrust
    2018-09-27 16:35:40.062248+0800 MOBFrameWorkTest[51074:1969808] ------->>>>>>是服务器信任的证书
    2018-09-27 16:35:40.382606+0800 MOBFrameWorkTest[51074:1969808] +++++++++++didReceiveResponse
    2018-09-27 16:35:40.382938+0800 MOBFrameWorkTest[51074:1969808] ----------->>>>>didReceiveData--62096-
    2018-09-27 16:35:40.383392+0800 MOBFrameWorkTest[51074:1969808] ++++++++++++++didCompleteWithError=---(null)
    
    
    

    NSURLProtectionSpace

    NSURLProtectionSpace对象表示需要身份验证的服务器或服务器的一部分。 保护空间定义了一系列匹配约束,用于确定应提供哪个凭证。

    这个类没有指定的初始化程序; 它的init方法总是返回nil。 你必须通过调用Creating a protection space描述的初始化方法之一来初始化此类

    
    //Creating a protection space
    - (instancetype)initWithHost:(NSString *)host port:(NSInteger)port protocol:(NSString *)protocol realm:(NSString *)realm authenticationMethod:(NSString *)authenticationMethod
    
    

    初始化一个保护空间对象,参数说明如下:


    image.png
    
    - (instancetype)initWithProxyHost:(NSString *)host port:(NSInteger)port type:(NSString *)type realm:(NSString *)realm authenticationMethod:(NSString *)authenticationMethod
    
    

    初始化代理服务器的保护空间对象。

    type:代理服务器的类型。 proxyType的值应设置为NSURLProtectionSpace Proxy Types(见后面)中指定的值之一。

    
    //Getting protection space properties
    @property(readonly, copy) NSString *authenticationMethod
    
    

    调用者的认证方法;

    
    @property(readonly, copy) NSArray<NSData *> *distinguishedNames
    
    

    可接受的进行客户端证书认证的证书颁发机构。
    如果保护空间的身份验证方法不是客户端证书,则为nil。 返回的颁发机构使用DER编码规则(DER)进行编码。

    
    @property(readonly, copy) NSString *host
    
    

    保护空间的主机名;

    
    @property(readonly) BOOL isProxy
    
    

    保护空间是否代表一个代理服务器;

    
    @property(readonly) NSInteger port
    
    

    保护空间的端口;

    
    @property(readonly, copy) NSString *protocol
    
    

    保护空间的协议;如果是代理服务器的保护空间,那么为nil;

    
    @property(readonly, copy) NSString *proxyType
    
    

    保护空间的代理类型;如果保护空间不是表示代理服务器,则为nil;

    
    @property(readonly, copy) NSString *realm
    
    

    保护空间的认证领域;
    如果没有设置域,则为nil。 通常只有HTTP和HTTPS身份验证时才使用此属性。

    
    @property(readonly) BOOL receivesCredentialSecurely
    
    

    一个布尔值,指示是否可以安全地发送保护空间的凭据。

    
    @property(readonly) SecTrustRef serverTrust
    
    

    表示服务器的SSL事务状态。
    如果保护空间的身份验证方法不是服务器信任的,则为nil。

    NSURLProtectionSpace Protocol Types

    这些常量描述了保护空间支持的协议;你可以在protocol属性找到这些值;

    • NSString *const NSURLProtectionSpaceHTT:HTTP
    • NSString *const NSURLProtectionSpaceHTTPS:HTTPS
    • NSString *const NSURLProtectionSpaceFTP: FTP
    NSURLProtectionSpace Proxy Types

    这些常量描述了代理的类型;你可以在proxyType属性找到这些值;

    • NSString *const NSURLProtectionSpaceHTTPProxy:HTTP代理
    • NSString *const NSURLProtectionSpaceHTTPSProxy:HTTPS代理
    • NSString *const NSURLProtectionSpaceFTPProxy:FTP代理
    • NSString *const NSURLProtectionSpaceSOCKSProxy:SOCKS代理
    NSURLProtectionSpace Authentication Methods

    这些常量描述了在初始化方法中可以用的认证方法,阐述如下:

    NSURLAuthenticationMethodDefault : 对协议使用默认身份验证方法
    NSURLAuthenticationMethodHTTPBasic:对保护空间使用HTTP基本身份验证,如果是HTTP请求,则此方法等同于NSURLAuthenticationMethodDefault
    NSURLAuthenticationMethodHTTPDigest : 对保护空间使用HTTP摘要身份验证
    NSURLAuthenticationMethodHTMLForm : 对保护空间使用HTML表单身份验证
    NSURLAuthenticationMethodNegotiate : 对保护空间使用Kerberos或NTLM身份验证
    NSURLAuthenticationMethodNTLM : 对保护空间使用NTLM身份验证
    NSURLAuthenticationMethodClientCertificate : 对保护空间使用客户端证书验证,该认证方法可以应用于任何协议。
    NSURLAuthenticationMethodServerTrust : 对保护空间执行服务器信任身份验证(证书验证)。此验证方法可以应用于任何协议,最常用于覆盖SSL和TLS链验证。
    NSURLAuthenticationMethodHTMLForm:URL加载系统从不会根据此认证方法发出认证挑战

    参考链接
    保护网络传输的实现
    https://www.jianshu.com/p/88336eab2b8d

    相关文章

      网友评论

          本文标题:iOS NSURLAuthenticationChallenge

          本文链接:https://www.haomeiwen.com/subject/vhaeoftx.html