当前位置: > > > Swift - 使用URLSession通过HTTPS进行网络请求,及证书的使用

Swift - 使用URLSession通过HTTPS进行网络请求,及证书的使用

(本文代码已升级至Swift3)
 
一,证书的生成,以及服务器配置
文章详细介绍了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
}
控制台打印输出如下:


4,只使用一个客户端证书
由于我们使用的是自签名的证书,那么对服务器的认证全由客户端这边判断。也就是说其实使用一个客户端证书“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
}

评论0