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

前言

随着网络技术的发展, 越来越多的公司开始使用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.https通信原理

https通信其实就是加密和解密的过程, 而其中既用到了对称加密(AES)又用到了非对称加密(RSA), 对称加密很简单就是有一串秘钥, 既可以用它加密又可以用它解密, 非对称加密是有一对秘钥, 称为公钥私钥, 使用公钥进行加密的必须使用私钥进行解密, 使用私钥加密的也必须使用公钥进行解密, 两个要是可以互换, 但是私钥要比公钥长一些, 所以公钥适合用于传输

https通信流程大概是这样的:

1.客户端访问一个网站后会获得服务器的 CA证书, 首先验证证书的有效性, 如果有效客户端就会取出里面的秘钥 -- 公钥(非对称加密)
2.然后客户端自己随机生成了一个未加密的对称加密密钥, 使用公钥对未加密的对称加密密钥进行加密生成加密之对称加密秘钥, 使用未加密的对称加密秘钥给要发送给服务器的数据进行加密生成加密数据, 然后把加密之对称加密秘钥加密数据发送给服务器就能与服务器通信了, 因为数据和秘钥都是加密的, 而且加密之对称加密秘钥只有服务器有配对的私钥才能解密
3.服务器收到加密之对称加密秘钥使用跟公钥配对的私钥(本身就存储在服务器上)进行解密得到未加密的对称加密秘钥, 使用未加密的对称加密秘钥就可以解密客户端发来的数据了
4.服务器收到客户端数据后, 再次发送的数据还是用未加密的对称加密秘钥进行加密后传给客户端, 客户端收到后用未加密的对称加密秘钥进行解密就能得到完整的数据了

二.快速开始

我会从前台后台两个角度依次讲解, 先说一下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])
Error Domain=NSURLErrorDomain Code=-1202 "The certificate for this server is invalid. You might be connecting to a server that is pretending to be “objcat.com” which could put your confidential information at risk." UserInfo={NSURLErrorFailingURLPeerTrustErrorKey=<SecTrustRef: 0x600001ff0c80>, NSErrorFailingURLKey=https://objcat.com/api/v1/hello, NSErrorFailingURLStringKey=https://objcat.com/api/v1/hello, NSLocalizedDescription=The certificate for this server is invalid. You might be connecting to a server that is pretending to be “objcat.com” which could put your confidential information at risk.}

还是这个

错误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.帮助后台对接调试

然后还有一点就是这个域名AF是验证不了的, 所以要设置为NO, 我猜测只有CA证书才能使用AF中的验证域名

那么有人会问了, 既然你设置了都不验证(有效性和域名)那还要证书有个屁用, 问得好, 我们怎么证明证书是有效的呢, 可以用一个任意证书放上去, 验证绝对是不通过的, 唯有服务器端的p12导出的cer可以使用, 所以可以证明证书有效

错误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

注意这里配置的域名需要和证书一模一样才行 本人亲自测试

如果host是127.0.0.1 www.objcat.com

那么上面的证书是可以使用的

但如果host是127.0.0.1 objcat.com

上面的证书会报出不安全

有人说这玩意有啥用啊, 随便配配就行了啊, 兄弟这关乎你的访问地址啊
https://www.objcat.com/api/v1/hello
https://objcat.com/api/v1/hello
这两个地址完全不一样好么

如果是想要下面那个样子, 你必须把证书创建为, 下面的样子才可以

-ext san=dns:objcat.com

创建为这个样子后, 经过我的测试www.objcat.comobjcat.com都可以使用, 如果使用前者域名谷歌浏览器会自动跳转到后者上面去, 应该是对有无www进行了处理, 如果www所在的地址不安全就尝试去掉www

综上所述 还是创建-ext san=dns:objcat.com不带www的比较好, 因为可以适配两种情况

完整命令

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335

推荐阅读更多精彩内容