使用Alamofire通過HTTPS進行網路請求及證書的使用

weixin_34402408發表於2017-02-13

本文介紹如何使用 Alamofire 來實現HTTPS網路請求,由於Alamofire就是對URLSession的封裝,所以實現起來區別不大。

  • 證書的生成以及伺服器配置
  • Alamofire使用HTTPS進行網路請求
    • 證書匯入
      通過客戶端瀏覽器訪問HTTPS服務需要安裝“mykey.p12”,“tomcat.cer”這兩個證書。同樣,我們開發的應用中也需要把這兩個證書新增進來。同時在 “工程” -> “Build Phases” -> “Copy Bundle Resources” 中新增這兩個證書檔案。
    • 配置Info.plist
      由於我們使用的是自簽名的證書,而蘋果ATS(App Transport Security)只信任知名CA頒發的證書,所以在iOS9下即使是HTTPS請求還是會被ATS攔截。
      所以在Info.plist下新增如下配置(iOS8不需要):
<key>NSAppTransportSecurity</key>
<dict>
    <key>NSAllowsArbitraryLoads</key>
    <true/>
</dict>
  • 使用兩個證書進行雙向驗證以及網路請求
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
}
  • 只使用一個客戶端證書
    由於我們使用的是自簽名的證書,那麼對伺服器的認證全由客戶端這邊判斷。也就是說其實使用一個客戶端證書“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
}

原文出自:www.hangge.com 轉載請保留原文連結:http://www.hangge.com/blog/cache/detail_1052.html

相關文章