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

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

(本文代码已升级至Swift3)
 
一,证书的生成,以及服务器配置
文章详细介绍了HTTPS,SSL/TLS。还有使用key tool生成自签名证书,Tomcat下https服务的配置。

二,iOS下APP使用HTTPS进行网络请求
1,证书导入
前面文章介绍了通过客户端浏览器访问HTTPS服务需,需要安装“mykey.p12”,“tomcat.cer”这两个证书。同样,我们开发的应用中也需要把这两个证书添加进来。
原文:Swift - 使用URLSession通过HTTPS进行网络请求,及证书的使用
记的同时在 “工程” -> “Build Phases” -> “Copy Bundle Resources” 中添加这两个证书文件。
原文:Swift - 使用URLSession通过HTTPS进行网络请求,及证书的使用

2,配置Info.plist
由于我们使用的是自签名的证书,而苹果ATS(App Transport Security)只信任知名CA颁发的证书,所以在iOS9下即使是HTTPS请求还是会被ATS拦截。
所以在Info.plist下添加如下配置(iOS8不需要):
1
2
3
4
5
<key>NSAppTransportSecurity</key>
<dict>
    <key>NSAllowsArbitraryLoads</key>
    <true/>
</dict>

3,使用两个证书进行双向验证,以及网络请求
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
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
}
控制台打印输出如下:
原文:Swift - 使用URLSession通过HTTPS进行网络请求,及证书的使用

4,只使用一个客户端证书
由于我们使用的是自签名的证书,那么对服务器的认证全由客户端这边判断。也就是说其实使用一个客户端证书“mykey.p12”也是可以的(项目中也只需导入一个证书)。
当对服务器进行验证的时候,判断服务主机地址是否正确,是的话信任即可(代码高亮部分)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
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