Alamofire https自簽名證書驗證

Alamofire,swift語言中,使用最廣的網(wǎng)絡(luò)請求框架,是AFNetworking的swift版本,AF其實就是Alamofire的縮寫,swift下叫回這個名字,也算是回歸本初了。

關(guān)于HTTPS的證書

圖片來自網(wǎng)絡(luò)

一般我們拿到后臺人員導(dǎo)出的證書,會用到兩個,一個是后綴名為cer的包含公鑰的CA證書,用來讓客戶端對服務(wù)器進行驗證;一個是后綴名為p12的客戶端證書,用來讓服務(wù)器對客戶端進行驗證。我們的電腦瀏覽器想要訪問服務(wù)器的話,直接安裝客戶端的p12證書就可以了,手動選擇信任服務(wù)器,瀏覽器會報不安全的警告。這里我們也能想到,我們的app在驗證的過程中,類似的也可以選擇雙向驗證或者忽略對服務(wù)器證書驗證的單項驗證。

Alamofire驗證證書

Alamofire將對https證書的驗證放在SessionManager.delegate.sessionDidReceiveChallenge這個block里進行,所以我們給這個block賦值做驗證:

//
//  HTTPSManager.swift
//  Test
//
//  Created by Fxxx on 2018/9/30.
//  Copyright ? 2018 Aaron Feng. All rights reserved.
//

import UIKit
import Alamofire

class HTTPSManager: NSObject {
    
   func setAlamofireHttps() {
        
        SessionManager.default.delegate.sessionDidReceiveChallenge = { (session: URLSession, challenge: URLAuthenticationChallenge) in
            
            let method = challenge.protectionSpace.authenticationMethod
            
            if method == NSURLAuthenticationMethodServerTrust {
                
                //驗證服務(wù)器,直接信任或者驗證證書二選一,推薦驗證證書,更安全
                return HTTPSManager.trustServerWithCer(challenge: challenge)
//                return HTTPSManager.trustServer(challenge: challenge)
                
            } else if method == NSURLAuthenticationMethodClientCertificate {
                
                //認證客戶端證書
                return HTTPSManager.sendClientCer()
                
            } else {
                
                //其他情況,不通過驗證
                return (.cancelAuthenticationChallenge, nil)
                
            }
            
        }
        
    }
    
    //不做任何驗證,直接信任服務(wù)器
    static private func trustServer(challenge: URLAuthenticationChallenge) -> (URLSession.AuthChallengeDisposition, URLCredential?) {
        
        let disposition = URLSession.AuthChallengeDisposition.useCredential
        let credential = URLCredential.init(trust: challenge.protectionSpace.serverTrust!)
        return (disposition, credential)
        
    }
    
    //驗證服務(wù)器證書
    static private func trustServerWithCer(challenge: URLAuthenticationChallenge) -> (URLSession.AuthChallengeDisposition, URLCredential?) {
        
        var disposition: URLSession.AuthChallengeDisposition = .performDefaultHandling
        var credential: URLCredential?
        
        //獲取服務(wù)器發(fā)送過來的證書
        let serverTrust:SecTrust = challenge.protectionSpace.serverTrust!
        let certificate = SecTrustGetCertificateAtIndex(serverTrust, 0)!
        let remoteCertificateData = CFBridgingRetain(SecCertificateCopyData(certificate))!
        
        //加載本地CA證書
        let cerPath = Bundle.main.path(forResource: "你本地的cer證書文件名", ofType: "cer")!
        let cerUrl = URL(fileURLWithPath:cerPath)
        let localCertificateData = try! Data(contentsOf: cerUrl)
        
        if (remoteCertificateData.isEqual(localCertificateData) == true) {
            
            //服務(wù)器證書驗證通過
            disposition = URLSession.AuthChallengeDisposition.useCredential
            credential = URLCredential(trust: serverTrust)
            
        } else {
            
            //服務(wù)器證書驗證失敗
            disposition = URLSession.AuthChallengeDisposition.cancelAuthenticationChallenge
            
        }
        
        return (disposition, credential)
        
    }
    
    //發(fā)送客戶端證書交由服務(wù)器驗證
    static private func sendClientCer() -> (URLSession.AuthChallengeDisposition, URLCredential?) {
        
        let disposition = URLSession.AuthChallengeDisposition.useCredential
        var credential: URLCredential?
        
        //獲取項目中P12證書文件的路徑
        let path: String = Bundle.main.path(forResource: "你本地的p12證書文件名", ofType: "p12")!
        let PKCS12Data = NSData(contentsOfFile:path)!
        let key : NSString = kSecImportExportPassphrase as NSString
        let options : NSDictionary = [key : "p12證書的密碼"] //客戶端證書密碼
        
        var items: CFArray?
        let error = SecPKCS12Import(PKCS12Data, options, &items)
        
        if error == errSecSuccess {
            
            let itemArr = items! as Array
            let item = itemArr.first!
            
            let identityPointer = item["identity"];
            let secIdentityRef = identityPointer as! SecIdentity
            
            let chainPointer = item["chain"]
            let chainRef = chainPointer as? [Any]
            
            credential = URLCredential.init(identity: secIdentityRef, certificates: chainRef, persistence: URLCredential.Persistence.forSession)
            
        }
        
        return (disposition, credential)
        
    }
    
}

在程序啟動進行網(wǎng)絡(luò)請求之前,調(diào)用一次setAlamofireHttps方法就好了。

image.png

PS:這里解釋一下為什么不把setAlamofireHttps方法設(shè)置為靜態(tài),因為這個方法實際上是在執(zhí)行一個block的賦值操作,只會被調(diào)用一次,static的生命周期是從整個程序運行開始到結(jié)束,所以除了會被頻繁使用的屬性或者方法,一般要謹慎使用static避免不必要的內(nèi)存浪費,其他幾個方法是每次網(wǎng)絡(luò)請求都會調(diào)用,所以設(shè)置成靜態(tài)比較合理。
整個類里既有靜態(tài)的類方法也有實例方法,看著別扭,那有人會問,可不可以把所有方法都設(shè)置成實例方法,可以是可以,但是由于setAlamofireHttps賦值的block中調(diào)用了后續(xù)的幾個方法,會持有self,這個block又是屬于SessionManager.default這個單例的引用鏈,所以你創(chuàng)建的這個HTTPSManager這個實例對象會被一直持有無法釋放,造成浪費。
所以綜合看來,這種混合方式性能最優(yōu)。

歡迎更正錯誤和交流,回復(fù)評論和私信皆可 ??

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容