Swift - 使用URLSession通过HTTPS进行网络请求,及证书的使用
(本文代码已升级至Swift3)
3,使用两个证书进行双向验证,以及网络请求
4,只使用一个客户端证书
一,证书的生成,以及服务器配置
参考我前面写的这篇文章:Tomcat服务器配置https双向认证(使用keytool生成证书)
文章详细介绍了HTTPS,SSL/TLS。还有使用key tool生成自签名证书,Tomcat下https服务的配置。
二,iOS下APP使用HTTPS进行网络请求
1,证书导入
前面文章介绍了通过客户端浏览器访问HTTPS服务需,需要安装“mykey.p12”,“tomcat.cer”这两个证书。同样,我们开发的应用中也需要把这两个证书添加进来。
记的同时在 “工程” -> “Build Phases” -> “Copy Bundle Resources” 中添加这两个证书文件。
2,配置Info.plist
由于我们使用的是自签名的证书,而苹果ATS(App Transport Security)只信任知名CA颁发的证书,所以在iOS9下即使是HTTPS请求还是会被ATS拦截。
所以在Info.plist下添加如下配置(iOS8不需要):
<key>NSAppTransportSecurity</key> <dict> <key>NSAllowsArbitraryLoads</key> <true/> </dict>
3,使用两个证书进行双向验证,以及网络请求
import UIKit import Foundation class ViewController: UIViewController, URLSessionDelegate { override func viewDidLoad() { super.viewDidLoad() //获取数据 httpGet(request: URLRequest(url: URL(string: "https://192.168.1.112:8443")!)) } // 使用URLSession请求数据 func httpGet(request: URLRequest) { let configuration = URLSessionConfiguration.default let session = URLSession(configuration: configuration, delegate: self, delegateQueue:OperationQueue.main) let dataTask = session.dataTask(with: request, completionHandler: {(data, response, error) -> Void in if error != nil{ print(error?.localizedDescription) }else{ let str = String(data: data!, encoding: String.Encoding.utf8) print("访问成功,获取数据如下:") print(str) } }) //使用resume方法启动任务 dataTask.resume() } // 在访问资源的时候,如果服务器返回需要授权(提供一个URLCredential对象) // 那么该方法就回被调用(这个是URLSessionDelegate代理方法) func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) { //认证服务器证书 if challenge.protectionSpace.authenticationMethod == (NSURLAuthenticationMethodServerTrust) { print("服务端证书认证!") let serverTrust:SecTrust = challenge.protectionSpace.serverTrust! let certificate = SecTrustGetCertificateAtIndex(serverTrust, 0)! let remoteCertificateData = CFBridgingRetain(SecCertificateCopyData(certificate))! let cerPath = Bundle.main.path(forResource: "tomcat", ofType: "cer")! let cerUrl = URL(fileURLWithPath:cerPath) let localCertificateData = try! Data(contentsOf: cerUrl) if (remoteCertificateData.isEqual(localCertificateData) == true) { let credential = URLCredential(trust: serverTrust) challenge.sender?.use(credential, for: challenge) completionHandler(URLSession.AuthChallengeDisposition.useCredential, URLCredential(trust: challenge.protectionSpace.serverTrust!)) } else { completionHandler(.cancelAuthenticationChallenge, nil) } } //认证客户端证书 else if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodClientCertificate { print("客户端证书认证!") //获取客户端证书相关信息 let identityAndTrust:IdentityAndTrust = self.extractIdentity() let urlCredential:URLCredential = URLCredential( identity: identityAndTrust.identityRef, certificates: identityAndTrust.certArray as? [AnyObject], persistence: URLCredential.Persistence.forSession) completionHandler(.useCredential, urlCredential) } // 其它情况(不接受认证) else { print("其它情况(不接受认证)") completionHandler(.cancelAuthenticationChallenge, nil); } } //获取客户端证书相关信息 func extractIdentity() -> IdentityAndTrust { var identityAndTrust:IdentityAndTrust! var securityError:OSStatus = errSecSuccess let path: String = Bundle.main.path(forResource: "mykey", ofType: "p12")! let PKCS12Data = NSData(contentsOfFile:path)! let key : NSString = kSecImportExportPassphrase as NSString let options : NSDictionary = [key : "123456"] //客户端证书密码 //create variable for holding security information //var privateKeyRef: SecKeyRef? = nil var items : CFArray? securityError = SecPKCS12Import(PKCS12Data, options, &items) if securityError == errSecSuccess { let certItems:CFArray = items as CFArray!; let certItemsArray:Array = certItems as Array let dict:AnyObject? = certItemsArray.first; if let certEntry:Dictionary = dict as? Dictionary<String, AnyObject> { // grab the identity let identityPointer:AnyObject? = certEntry["identity"]; let secIdentityRef:SecIdentity = identityPointer as! SecIdentity! print("\(identityPointer) :::: \(secIdentityRef)") // grab the trust let trustPointer:AnyObject? = certEntry["trust"] let trustRef:SecTrust = trustPointer as! SecTrust print("\(trustPointer) :::: \(trustRef)") // grab the cert let chainPointer:AnyObject? = certEntry["chain"] identityAndTrust = IdentityAndTrust(identityRef: secIdentityRef, trust: trustRef, certArray: chainPointer!) } } return identityAndTrust; } } //定义一个结构体,存储认证相关信息 struct IdentityAndTrust { var identityRef:SecIdentity var trust:SecTrust var certArray:AnyObject }控制台打印输出如下:
由于我们使用的是自签名的证书,那么对服务器的认证全由客户端这边判断。也就是说其实使用一个客户端证书“mykey.p12”也是可以的(项目中也只需导入一个证书)。
当对服务器进行验证的时候,判断服务主机地址是否正确,是的话信任即可(代码高亮部分)
import UIKit import Foundation class ViewController: UIViewController, URLSessionDelegate { //自签名网站地址 let selfSignedHosts = ["192.168.1.112", "www.hangge.com"] override func viewDidLoad() { super.viewDidLoad() //获取数据 httpGet(request: URLRequest(url: URL(string: "https://192.168.1.112:8443")!)) } // 使用URLSession请求数据 func httpGet(request: URLRequest) { let configuration = URLSessionConfiguration.default let session = URLSession(configuration: configuration, delegate: self, delegateQueue:OperationQueue.main) let dataTask = session.dataTask(with: request, completionHandler: {(data, response, error) -> Void in if error != nil{ print(error?.localizedDescription) }else{ let str = String(data: data!, encoding: String.Encoding.utf8) print("访问成功,获取数据如下:") print(str) } }) //使用resume方法启动任务 dataTask.resume() } // 在访问资源的时候,如果服务器返回需要授权(提供一个URLCredential对象) // 那么该方法就回被调用(这个是URLSessionDelegate代理方法) func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) { //认证服务器(这里不使用服务器证书认证,只需地址是我们定义的几个地址即可信任) if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust && self.selfSignedHosts.contains(challenge.protectionSpace.host) { print("服务器认证!") let credential = URLCredential(trust: challenge.protectionSpace.serverTrust!) completionHandler(.useCredential, credential) } //认证客户端证书 else if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodClientCertificate { print("客户端证书认证!") //获取客户端证书相关信息 let identityAndTrust:IdentityAndTrust = self.extractIdentity() let urlCredential:URLCredential = URLCredential( identity: identityAndTrust.identityRef, certificates: identityAndTrust.certArray as? [AnyObject], persistence: URLCredential.Persistence.forSession) completionHandler(.useCredential, urlCredential) } // 其它情况(不接受认证) else { print("其它情况(不接受认证)") completionHandler(.cancelAuthenticationChallenge, nil); } } //获取客户端证书相关信息 func extractIdentity() -> IdentityAndTrust { var identityAndTrust:IdentityAndTrust! var securityError:OSStatus = errSecSuccess let path: String = Bundle.main.path(forResource: "mykey", ofType: "p12")! let PKCS12Data = NSData(contentsOfFile:path)! let key : NSString = kSecImportExportPassphrase as NSString let options : NSDictionary = [key : "123456"] //客户端证书密码 //create variable for holding security information //var privateKeyRef: SecKeyRef? = nil var items : CFArray? securityError = SecPKCS12Import(PKCS12Data, options, &items) if securityError == errSecSuccess { let certItems:CFArray = items as CFArray!; let certItemsArray:Array = certItems as Array let dict:AnyObject? = certItemsArray.first; if let certEntry:Dictionary = dict as? Dictionary<String, AnyObject> { // grab the identity let identityPointer:AnyObject? = certEntry["identity"]; let secIdentityRef:SecIdentity = identityPointer as! SecIdentity! print("\(identityPointer) :::: \(secIdentityRef)") // grab the trust let trustPointer:AnyObject? = certEntry["trust"] let trustRef:SecTrust = trustPointer as! SecTrust print("\(trustPointer) :::: \(trustRef)") // grab the cert let chainPointer:AnyObject? = certEntry["chain"] identityAndTrust = IdentityAndTrust(identityRef: secIdentityRef, trust: trustRef, certArray: chainPointer!) } } return identityAndTrust; } } //定义一个结构体,存储认证相关信息 struct IdentityAndTrust { var identityRef:SecIdentity var trust:SecTrust var certArray:AnyObject }