通过Authentication Challenge来信任自签名Https证书
在开发阶段我们我们经常使用自签名的证书来部署我们的后台rest api。但是在iOS中调用的时候就会因为证书不被信任而调用api不成功。这时候我们就需要通过实现某些网络回调函数来自定义证书的验证逻辑。(在iOS中一般通过UrlSession(OC中是NSUrlSession)来进行网络通信,这里以UrlSession为例)。首先我们需要了解几个概念。
Challenge
Challenge是计算机安全中的专业术语。字面意思质询。就是为了验证用户身份,向访问者发送一个质询,然后访问者需要提供一个正确的回答以示身份。最简单的就是我们访问一个需要授权的网站,网站后台会通过HTTP协议想浏览器发送一个质询,要求用户输入用户名密码。(浏览器会弹出一个对话框供用户输入)
在iOS的网络相关库中,提供了如下几个类来描述Challenge的过程中的抽象实体。
URLProtectionSpace
这个表示服务器上的一块受保护的区域,访问这一块需要进行质询。他有如下常用属性:
// realm是ProtectionSpace的标示符,服务器上的一组资源通过realm来标示成一组采用相同验证方式的资源(ProtectionSpace)。 @property (nullable, readonly, copy) NSString *realm; // 资源所在的服务器 @property (readonly, copy) NSString *host; // 资源所在服务器端口 @property (readonly) NSInteger port; //获取资源的协议资源 @property (nullable, readonly, copy) NSString *protocol; // 质询所采用验证方式 @property (readonly, copy) NSString *authenticationMethod;
质询验证方式有如下几种是常用的:
NSURLAuthenticationMethodHTTPBasic //HTTP基本验证,服务器向客户端询问用户名,密码
NSURLAuthenticationMethodClientCertificate//客户端证书验证,服务器向客户端询客户端身份证书
NSURLAuthenticationMethodServerTrust//服务器端证书验证,客户端对服务器端的证书进行验证。HTTPS中的服务器端证书验证属于这一种。
URLAuthenticationChallenge
这就是服务器端对客户端的一次质询的描述了。它有如下常用属性:
//该质询所对应的ProtectionSpace @property (readonly, copy) NSURLProtectionSpace *protectionSpace; //表示该质询的发送方 @property (nullable, readonly, retain) id<NSURLAuthenticationChallengeSender> sender;
UrlCredential
他是客户端对服务器端质询的响应。根据验证方式不一样,有如下几种UrlCredential:
- 基于用户名密码的UrlCredential
- 基于客户端证书的UrlCredential
- 基于服务器端证书的UrlCredential //就是我们这里验证服务器端的证书要用到的
它们分别对应于UrlCredential的三种构造方式。详情参考Apple开发文档
SecTrust
他是iOS中对证书和Accept Policy的包装。系统对后台证书验证实际上是对该对象的验证。详情建Apple开发文档
好了,到这里所有的概念性的东西都说完了。最后就是要把这些概念全部组合到UrlSession的一个回调方法中来自行验证证书。代码如下:
//这是NSUrlSessionDelegate总定义的函数,因此先需要设置NSUrlSession对象的delegate,然后再delegate对象中实现该方法
- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler {
//这里检查质询的验证方式是否是服务器端证书验证 if([challenge.protectionSpace.authenticationMethod isEqualToString: NSURLAuthenticationMethodServerTrust]) {
//这里直接把证书包装对象拿到, SecTrustRef trustRef = [challenge.protectionSpace serverTrust];
// 使用证书SecTrust对象来构造URLCredential,系统默认实现这里应该是要对SecTrust进行验证,而此处我们的目的就是要信任所有证书。因此跳过验证这一步。 id trustCredential = [NSURLCredential credentialForTrust:trustRef];
//通过回调函数告诉系统对于该质询的UrlCredential. completionHandler(NSURLSessionAuthChallengeUseCredential, trustCredential); } else { completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil); } }