美文网首页iOS开发之常用技术点高能GXiOS
[iOS] 教你优雅的访问HTTPS接口

[iOS] 教你优雅的访问HTTPS接口

作者: objcat | 来源:发表于2019-01-10 10:54 被阅读167次

    前言

    随着网络技术的发展, 越来越多的公司开始使用https作为网络请求协议, 但是身为这个时代的开发者, 却很少有人了解其中的原理, 每次调接口的时候都是浪费大量的时间来对接, 联调, 排查错误, 经常会说的一句, 接口怎么不通, 而这个地方的资料虽然产量高但每一篇都很重量级, 导致了开发者学习困难, 所以这篇文章产生了, 我会用解决问题的方式来推进文章, 由浅入深, 依次讲解, 下面就跟着我们的文章一起来看吧.

    一.概念

    百度上一大堆, 想看概念的可以先查一下, 这里使用普通话来叙述.

    http:
    全名: Hyper Text Transfer Protocol
    描述: 常用的网络协议

    https:
    全名: Hyper Text Transfer Protocol over Secure Socket Layer
    描述: 常用的网络协议, 比http更安全

    1.https安全在什么地方呢?

    下面就要引入一个概念ssl, 全名(Secure Sockets Layer 安全套接层), 它是一种安全协议, 协议原理是在传输层对请求信息进行了加密, 这样别人就不太会轻易看到你发送的信息了.

    https = http + ssl

    2.ssl证书

    读了上文已经知道了, ssl就是https协议的加密核心, 那我要怎么使用它呢,
    下面就要介绍主角ssl证书了, ssl证书的种类有两种, 一种是自建证书, 一种是CA证书, 自建证书顾名思义就是自己制作出来的, CA证书则是从正规的证书颁发机构花钱买的或免费申请的, 网络请求过程大概是这样的, 发送请求的时候使用证书里面的秘钥对请求信息做加密处理, 到达客户端再用秘钥进行解密, 这两个步骤都是系统帮你自动完成的, 因为存在这个过程所以https通常来讲请求耗时更长, 不过为了安全, 这些牺牲还是可以接受的.

    二.快速开始

    我会从前台后台两个角度依次讲解, 先说一下iOS端如何正常访问https接口, 再来说一下JAVA后台是怎么配置证书来实现https访问的.

    1.iOS配置https

    CA证书:

    上面已经说过了, ssl证书一共分为两种, 自建证书CA证书, 那么对于我们来说CA证书自然是最省事的, 可以用一句话概括, 跟正常网络请求没有任何区别.

    甚至你连这个都不用改动, YES和NO, 均可以访问通过.(亲测)

    下面上AF代码 , 正常的不能再正常了 - -

    AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
    [manager GET:url parameters:nil progress:^(NSProgress * _Nonnull downloadProgress) {
    } success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
        NSLog(@"%@", responseObject);
    } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
        NSLog(@"%@", error);
    }];
    

    所以公司使用CA证书你就偷着乐去吧, 对开发完全没影响.

    自建证书:

    这个比起CA证书就要麻烦很多了, 不过只要学会了方法还是很好解决的.

    你通常会遇到的错误是这个

    错误1

    Task <6A6DB526-EC21-4E85-BACC-1415D23C3057>.<1> load failed with error Error Domain=NSURLErrorDomain Code=-999 "cancelled" UserInfo={NSErrorFailingURLStringKey=https://www.objcat.com:8082/hello, NSErrorFailingURLKey=https://www.objcat.com:8082/hello, _NSURLErrorRelatedURLSessionTaskErrorKey=(
        "LocalDataTask <6A6DB526-EC21-4E85-BACC-1415D23C3057>.<1>"
    ), _NSURLErrorFailingURLSessionTaskErrorKey=LocalDataTask <6A6DB526-EC21-4E85-BACC-1415D23C3057>.<1>, NSLocalizedDescription=cancelled} [-999]
    2019-01-09 15:37:31.069920+0800 https网络测试[23697:794090] Error Domain=NSURLErrorDomain Code=-999 "cancelled" UserInfo={NSErrorFailingURLStringKey=https://www.objcat.com:8082/hello, NSErrorFailingURLKey=https://www.objcat.com:8082/hello, _NSURLErrorRelatedURLSessionTaskErrorKey=(
        "LocalDataTask <6A6DB526-EC21-4E85-BACC-1415D23C3057>.<1>"
    ), _NSURLErrorFailingURLSessionTaskErrorKey=LocalDataTask <6A6DB526-EC21-4E85-BACC-1415D23C3057>.<1>, NSLocalizedDescription=cancelled}
    2019-01-09 15:37:31.075854+0800 https网络测试[23697:794147] Task <6A6DB526-EC21-4E85-BACC-1415D23C3057>.<1> finished with error - code: -999
    2019-01-09 15:37:31.077351+0800 https网络测试[23697:794148] Task <6A6DB526-EC21-4E85-BACC-1415D23C3057>.<1> HTTP load failed (error code: -999 [1:89])
    

    还是这个

    错误2

    Error Domain=NSURLErrorDomain Code=-1200 "An SSL error has occurred and a secure connection to the server cannot be made." UserInfo={NSLocalizedRecoverySuggestion=Would you like to connect to the server anyway?, _kCFStreamErrorDomainKey=3, NSErrorPeerCertificateChainKey=(
        "<cert(0x7f86d2017400) s: Andy i: Andy>"
    ), NSErrorClientCertificateStateKey=0, NSErrorFailingURLKey=https://www.objcat.com:8082/hello, NSErrorFailingURLStringKey=https://www.objcat.com:8082/hello, NSUnderlyingError=0x600001f21500 {Error Domain=kCFErrorDomainCFNetwork Code=-1200 "(null)" UserInfo={_kCFStreamPropertySSLClientCertificateState=0, kCFStreamPropertySSLPeerTrust=<SecTrustRef: 0x60000230f2a0>, _kCFNetworkCFStreamSSLErrorOriginalValue=-9802, _kCFStreamErrorDomainKey=3, _kCFStreamErrorCodeKey=-9802, kCFStreamPropertySSLPeerCertificates=(
        "<cert(0x7f86d2017400) s: Andy i: Andy>"
    )}}, _NSURLErrorRelatedURLSessionTaskErrorKey=(
        "LocalDataTask <F8DB0C28-4CD3-417B-B89B-B0DCCABB6EA6>.<1>"
    ), _kCFStreamErrorCodeKey=-9802, _NSURLErrorFailingURLSessionTaskErrorKey=LocalDataTask <F8DB0C28-4CD3-417B-B89B-B0DCCABB6EA6>.<1>, NSURLErrorFailingURLPeerTrustErrorKey=<SecTrustRef: 0x60000230f2a0>, NSLocalizedDescription=An SSL error has occurred and a secure connection to the server cannot be made.}
    

    或是这个

    错误3

    *** Terminating app due to uncaught exception 'Invalid Security Policy', reason: 'A security policy configured with `AFSSLPinningModeCertificate` can only be applied on a manager with a secure base URL (i.e. https)'
    *** First throw call stack:
    (
        0   CoreFoundation                      0x000000010b27029b __exceptionPreprocess + 331
        1   libobjc.A.dylib                     0x000000010a80c735 objc_exception_throw + 48
        2   https网络测试                   0x0000000109426325 -[AFHTTPSessionManager setSecurityPolicy:] + 613
        3   https网络测试                   0x000000010942276c -[ViewController viewDidLoad] + 172
        4   UIKitCore                           0x000000010f379781 -[UIViewController loadViewIfRequired] + 1186
        5   UIKitCore                           0x000000010f379be0 -[UIViewContro
    

    先把下巴合上, 口水擦一擦, 我们下面就来说一下这几个错误都是什么, 该怎么解决.

    错误1 解决方案:

    这里提供一个最好的解法, 也是最简单的, 可以用一个成语来形容 - 敞门入场, 我什么都不验证, 只想拿到我的数据, 其他跟我没关系.

    首先设置下面两项

        AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
        // 是否允许无效证书, 默认为NO
        manager.securityPolicy.allowInvalidCertificates = YES;
        // 是否校验域名, 默认为YES
        manager.securityPolicy.validatesDomainName = NO;
    

    然后这个地方设置为YES, Allow Arbitrary Loads 允许任意加载

    上述设置任何网络都可以保证通畅访问, 如果是来找答案的, 恭喜你问题已经解决了.

    下面内容有可能引起不适 慎读:

    错误2 解决方案

    你在寻找答案的时候有可能看到另外一种解法, 就是在AF中配置证书来保持访问正常, 说实话这种方法是完全不推荐的, 在强调一遍, 让公司去买CA证书.

    你可能对下面的代码很熟悉, 摘自互联网

    - (AFSecurityPolicy *)customSecurityPolicy {
        
        // 先导入证书 证书由服务端生成,具体由服务端人员操作
        NSString *cerPath = [[NSBundle mainBundle] pathForResource:@"keystore" ofType:@"cer"];//证书的路径 xx.cer
        NSData *cerData = [NSData dataWithContentsOfFile:cerPath];
        
        // AFSSLPinningModeCertificate 使用证书验证模式
        AFSecurityPolicy *securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate];
        // allowInvalidCertificates 是否允许无效证书(也就是自建的证书),默认为NO
        // 如果是需要验证自建证书,需要设置为YES
        securityPolicy.allowInvalidCertificates = YES;
        
        //validatesDomainName 是否需要验证域名,默认为YES;
        //假如证书的域名与你请求的域名不一致,需把该项设置为NO;如设成NO的话,即服务器使用其他可信任机构颁发的证书,也可以建立连接,这个非常危险,建议打开。
        //置为NO,主要用于这种情况:客户端请求的是子域名,而证书上的是另外一个域名。因为SSL证书上的域名是独立的,假如证书上注册的域名是www.google.com,那么mail.google.com是无法验证通过的;当然,有钱可以注册通配符的域名*.google.com,但这个还是比较贵的。
        //如置为NO,建议自己添加对应域名的校验逻辑。
        securityPolicy.validatesDomainName = NO;
        
        securityPolicy.pinnedCertificates = [[NSSet alloc] initWithObjects:cerData, nil];
        return securityPolicy;
    }
    

    路人甲: 对对对, 就是这垃圾东西, 我调了好几天.
    路人乙: 麻蛋的, 我写的都对, 咋访问不通呢, 是不是后台证书给错了.

    解决方法也简单
    1.让后台提供有效的ssl证书, 亲测p12导出cer是完全可以使用的
    2.检查你的代码有没有开启 Allow Arbitrary Loads = YES
    3.帮助后台对接调试

    错误2就会在没有开启Allow Arbitrary Loads的时候出现, 因为自建证书并非正规的ssl请求, 所以需要开启允许所有访问.

    然而在你做完这些之后会遇到错误3

    A security policy configured with `AFSSLPinningModeCertificate` can only be applied on a manager with a secure base URL (i.e. https)'
    

    意思是使用AFSSLPinningModeCertificate模式必须配置一个baseURL, 这个可以是你的域名或者ip, 目前还不知道有什么用, 随便配置一个即可.

    AFHTTPSessionManager *manager = [[AFHTTPSessionManager manager] initWithBaseURL:[NSURL URLWithString:@"https://www.baidu.com"]];
    

    如: https://www.baidu.com

    之后如果没什么失误的话, 就可以访问接口了...

    到这里告一段落.

    2.Java配置https

    CA证书:

    首先来介绍一下CA证书:

    一共分为三种

    DV SSL
    DV SSL证书是只验证网站域名所有权的简易型SSL证书,可10分钟快速颁发,能起到加密传输的作用,但无法向用户证明网站的真实身份。

    目前市面上的免费证书都是这个类型的, 只是提供了对数据的加密, 但是对提供证书的个人和机构的身份不做验证。

    例如我自己的证书:

    OV SSL
    OV SSL, 提供加密功能, 对申请者做严格的身份审核验证和DV SSL的区别在于, OV SSL 提供了对个人或者机构的审核, 能确认对方的身份, 安全性更高.

    所以这部分的证书申请是收费的~

    例如百度的证书:

    EV SSL
    EV最安全, 最严格, 超安EV SSL证书遵循全球统一的严格身份验证标准,是目前业界安全级别最高的顶级SSL证书, 验证过身份的公司名称可以直接显示在浏览器上, 是不是很高大上.

    例如证书颁发机构的官网:

    好了下面我们就来申请一个免费的证书吧!

    首先去阿里云或别的机构申请CA证书, 这里举例申请一个免费的.

    阿里云控制台 -> 产品与服务 -> 搜索ssl

    可以看到证书很贵, 这里面有一个免费的, 但是点出来需要点技巧

    之后选择支付就可以了, 支付完成会发现多了个证书

    填写基本信息

    然后点验证就可以了, 需要等待一些时间

    ------ 一段时间后 --------

    我们可以看到证书审核好了, 我们就可以直接配置了, 首先把证书下载下来, 密码在同目录的文本文件里, 然后开始配置, 这里以springcloud为例

    下载后的证书是pfx格式的, 我们先把它导入钥匙串, 然后导出为p12文件

    然后把证书放到resources目录下, 我改了个名请忽略 - -

    server:
      #服务端口号
      port: 8082
      ssl:
        key-store: classpath:haha.p12
        key-store-password: 123456
        key-store-type: PKCS12
    

    之后配置这些即可, 项目会自动开启https.

    之后访问接口试试

    我们可以看到证书是有效的, 这个服务器是配置在我本地的, 我们用ip来访问一下.

    我们会发现, 同样的接口, 把域名换成ip就不可以访问了, 这也验证了我们DV证书是校验域名的这个原理.

    我们可以直接在证书上查看我们信任的域名

    自建证书:

    自建证书配置也十分简单, 首先用java自带的keytool来生成一个自建证书

    keytool -genkey -alias tomcat -dname "CN=objcat,OU=objcat,O=objcat,L=PuDongXinQu,ST=ShangHai,C=CN"  -storetype PKCS12 -keyalg RSA -keysize 2048  -keystore keystore.p12 -validity 3650
    

    -genkey: 制作证书固有命令

    -alias: 别名

    -dname: 填写证书基本信息

    CN(Common Name 名字与姓氏)
    OU(Organization Unit 组织单位名称)
    O(Organization 组织名称)
    L(Locality 城市或区域名称)
    ST(State 省/市/自治区名称)
    

    -storetype: 证书保存类型
    -keyalg: 加密方式
    -keysize: 秘钥长度
    -keystore: 证书导出时的名称
    -validity: 证书有效期

    然后输入证书密码就可以完成创建了!

    双击证书, 输入密码就能导入钥匙串了, 我们来看看

    这个证书的使用方法和CA证书一样
    1.放在resources目录下
    2.填写配置文件 - 参考CA证书配置

    启动服务!

    访问接口
    https://localhost:8082/hello

    之后点击继续前往

    这样虽然可以访问, 但是我们可以看到上面写的三个红色的大字, 不安全, 所以我们需要解决一下这个问题, 就是信任证书!

    我们发现信任证书之后safari是可以访问通的, 但是chrome还是会报错不安全

    Certificate - Subject Alternative Name missing
    The certificate for this site does not contain a Subject Alternative Name extension containing a domain name or IP address.
    
    Certificate - missing
    This site is missing a valid, trusted certificate (net::ERR_CERT_COMMON_NAME_INVALID).
    

    然后我们就开始着手解决这两个问题

    根据错误提示, 是因为我的证书没有做域名认证造成的, 所以我们在生成证书的时候给它添加一个域名

    加上下面这句即可

    -ext san=dns:www.objcat.com
    

    完整命令

    keytool -genkey -alias tomcat -dname "CN=objcat,OU=objcat,O=objcat,L=PuDongXinQu,ST=ShangHai,C=CN"  -storetype PKCS12 -keyalg RSA -keysize 2048  -keystore keystore.p12 -validity 3650 -ext san=dns:www.objcat.com
    

    我们再次生成证书, 然后重新配置

    之后我们来修改 www.objcat.com 映射到本机域名

    127.0.0.1 www.objcat.com
    

    然后重启服务器

    这次试用域名访问接口试试

    我们发现问题解决了!

    finally enjoy it.

    by objcat 2019.1.10

    相关文章

      网友评论

        本文标题:[iOS] 教你优雅的访问HTTPS接口

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