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

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

(本文代码已升级至Swift3) 

我原来写过一篇文章介绍如何使用证书通过SSL/TLS方式进行网络请求(Swift - 使用URLSession通过HTTPS进行网络请求,及证书的使用),当时用的是 URLSession
本文介绍如何使用 Alamofire 来实现HTTPS网络请求,由于Alamofire就是对URLSession的封装,所以实现起来区别不大。
(如果Alamofire的配置使用不了解的,可以先去看看我原来写的文章:Swift - HTTP网络操作库Alamofire使用详解

一,证书的生成,以及服务器配置
文章详细介绍了HTTPS,SSL/TLS。还有使用key tool生成自签名证书,Tomcat下https服务的配置。

二,Alamofire使用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 Alamofire

class ViewController: UIViewController {
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        //认证相关设置
        let manager = SessionManager.default
        manager.delegate.sessionDidReceiveChallenge = { session, challenge in
            //认证服务器证书
            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)
                    return (URLSession.AuthChallengeDisposition.useCredential,
                            URLCredential(trust: challenge.protectionSpace.serverTrust!))
                    
                } else {
                    return (.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);
                
                return (.useCredential, urlCredential);
            }
            // 其它情况(不接受认证)
            else {
                print("其它情况(不接受认证)")
                return (.cancelAuthenticationChallenge, nil)
            }
        }
        
        //数据请求
        Alamofire.request("https://192.168.1.112:8443")
            .responseString { response in
                print(response)
        }
    }
    
    //获取客户端证书相关信息
    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;
    }
    
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }
}

//定义一个结构体,存储认证相关信息
struct IdentityAndTrust {
    var identityRef:SecIdentity
    var trust:SecTrust
    var certArray:AnyObject
}
控制台打印输出如下:

4,只使用一个客户端证书
由于我们使用的是自签名的证书,那么对服务器的认证全由客户端这边判断。也就是说其实使用一个客户端证书“mykey.p12”也是可以的(项目中也只需导入一个证书)。
当对服务器进行验证的时候,判断服务主机地址是否正确,是的话信任即可(代码高亮部分)
import UIKit
import Alamofire

class ViewController: UIViewController {
    
    //自签名网站地址
    let selfSignedHosts = ["192.168.1.112", "www.hangge.com"]
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        //认证相关设置
        let manager = SessionManager.default
        manager.delegate.sessionDidReceiveChallenge = { session, challenge in
            //认证服务器(这里不使用服务器证书认证,只需地址是我们定义的几个地址即可信任)
            if challenge.protectionSpace.authenticationMethod
                == NSURLAuthenticationMethodServerTrust
                && self.selfSignedHosts.contains(challenge.protectionSpace.host) {
                print("服务器认证!")
                let credential = URLCredential(trust: challenge.protectionSpace.serverTrust!)
                return (.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);
                
                return (.useCredential, urlCredential);
            }
            // 其它情况(不接受认证)
            else {
                print("其它情况(不接受认证)")
                return (.cancelAuthenticationChallenge, nil)
            }
        }
        
        //数据请求
        Alamofire.request("https://192.168.1.112:8443")
            .responseString { response in
                print(response)
        }
    }
    
    //获取客户端证书相关信息
    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;
    }
    
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }
}

//定义一个结构体,存储认证相关信息
struct IdentityAndTrust {
    var identityRef:SecIdentity
    var trust:SecTrust
    var certArray:AnyObject
}
评论13
  • 13楼
    2017-07-15 12:08
    布袋

    现在的服务器是用阿里云的 证书是免费的 ,ios客户端是用 swift 3.0 的
    请问航哥一下,Xcode客户端要单独再配置什么吗?
    还是什么也不做,直接把之前的 http,换成 https就可以了呢?

    站长回复

    阿里云证书不太清楚,可以先试试看,感觉应该不用特别处理。

  • 12楼
    2017-06-23 11:00
    溜溜

    航哥~如果服务器端使用的CA购买的证书的话,ios端也需要做以上的操作吗?求航哥翻牌子

    站长回复

    不知道你购买的是哪个机构的证书。通常如果服务端使用的是付费CA证书的话,iOS端这边是不用再特别处理的(系统中已经预装了可信根证书),直接https请求即可。

  • 11楼
    2017-03-30 17:06
    Liu

    站长好,请问下,Alamofire4.4(应该是最新版本),支持IPV6么,需不需要我们做些什么来让它支持

    站长回复

    支持IPV6的,只要正常使用即可。

  • 10楼
    2017-02-14 10:07
    龙飞

    请问下站长,这也viewcontroller在那写???怎么调用?? 我写成nsobject 验证部分不走

    站长回复

    验证是SessionManager的代理方法,每次请求网络的时候自动会调用的。

  • 9楼
    2017-01-13 06:21
    chai

    站长哥哥,我用这个代码去只进行cer的认证可以吗?code -999是怎么回事呀

    站长回复

    就本文样例而言,p12证书是一定要的,cer证书可以不需要。

  • 8楼
    2016-12-26 16:07
    Springhand

    航歌,非常赞、站长力战群雄!!!

    站长回复

    谢谢你的支持和鼓励。欢迎常来看看,我会持续更新的。

  • 7楼
    2016-12-19 18:55
    davy

    看了不下5次,终于解决了,谢谢博主

    站长回复

    不客气,问题解决了就好。

  • 6楼
    2016-12-19 17:06
    安静

    你好,问一下,我在查阅资料的时候,如果公司服务器使用的证书是受信任的CA证书,那么客户端只需要把http改成https就可以了,对吗?

    站长回复

    是这样的,受信任的证书直接使用就好了。

  • 5楼
    2016-12-16 09:46
    紫杉颖冰

    评论发布不了。。。

    站长回复

    可以评论啊。不过为了防止广告和垃圾信息,评论要审核后才能看到。

  • 4楼
    2016-12-16 09:40
    紫杉颖冰

    站长好,我想请问一下,项目里 加入了你写的这个Alamofire通过HTTPS进行网络请求的代码后,然后项目书写网络请求的时候 是不是还是按照原来请求的写法啊?? 谢谢

    站长回复

    只要网络请求都是使用Alamofire来请求的就可以了。

  • 3楼
    2016-12-13 15:50
    一直很安静

    看完后有点晕 = = 不是很理解,但还是很感谢

    站长回复

    这个代码看起来是有些复杂,多看看,应该能慢慢理解。

  • 2楼
    2016-11-29 17:19
    Dolphin

    文章写的很赞 ,好想问整个网站都是你来弄的么 Orz

    站长回复

    谢谢夸奖,目前网站就我一个。

  • 1楼
    2016-11-04 02:55
    mario

    可以更新一下swift3和Alamofire4.0.1之后的代码吗?里面很多都已经变了

    站长回复

    文章代码已更新,你可以再看下。