對AFNetworking進行解耦 AFNetworking解耦後能夠分爲如下幾個模塊: 1. NSURLSession:主要的一個基於NSURLSession的管理模塊; 2. Reachability:網絡監測模塊; 3. Security:Https驗證模塊; 4. Serialization:序列化模塊,包含了請求和響應的序列化; 5. UIKit:包含了一些UI的擴展,方便調用。api
NSURLSession 和 Reachability 還有 Serialization 、UIKit 這些模塊就不作過多的解析了,重點了解一下 Security中Https的認證。瀏覽器
裏面有關鍵的一步:即客戶端驗證服務端返回的公鑰證書,這就是https的驗證:安全
- (void)URLSession:(NSURLSession *)session
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
複製代碼
只要是Https的請求,就會走到這個方法,該方法的做用就是處理服務器返回的證書, 須要在該方法中告訴系統是否須要安裝服務器返回的證書,安裝即表明公鑰證書驗證經過。bash
咱們來經過AFNetworking的源碼來查看一下它是怎麼處理的,源碼裏有詳細註釋:服務器
- (void)URLSession:(NSURLSession *)session
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
{
//NSURLAuthenticationChallenge 須要處理的挑戰,裏面包含了一個類型爲 NSURLProtectionSpace 的屬性
//NSURLProtectionSpace裏面包含了 host port authenticationMethod等屬性,還有服務器用於https驗證的一些信息好比公鑰、random_s等,這些都是服務器傳過來的,authenticationMethod表示受權方式,對於AFNetworking來講,是經過比較NSURLAuthenticationMethodServerTrust來決定是否須要證書驗證
/* 處理方式
NSURLSessionAuthChallengeUseCredential:使用指定證書
NSURLSessionAuthChallengePerformDefaultHandling:默認方式處理
NSURLSessionAuthChallengeCancelAuthenticationChallenge:取消挑戰
NSURLSessionAuthChallengeRejectProtectionSpace:拒絕挑戰,並嘗試下一個驗證保護空間,忽略證書參數
*/
NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
/* NSURLCredential爲一個證書,它決定了咱們如何處理這個挑戰,它的初始化方法有:
//通常用於須要處理身份驗證401的錯誤驗證
- (instancetype)initWithUser:(NSString *)user password:(NSString *)password persistence:(NSURLCredentialPersistence)persistence
//經過鑰匙串中取得一個客戶端證書來建立證書,通常用於服務端進行雙向認證
+ (NSURLCredential *)credentialWithIdentity:(SecIdentityRef)identity certificates:(nullable NSArray *)certArray persistence:(NSURLCredentialPersistence)persistence
//經過服務端傳回的SecTrustRef(包含待驗證的證書和支持的驗證方法等)對象建立證書,通常客戶端須要驗證服務端身份時使用,即Https中的客戶端對服務端證書驗證這一步,收到服務端的challenge,例如https須要驗證證書等 ats開啓
- (instancetype)initWithTrust:(SecTrustRef)trust
*/
__block NSURLCredential *credential = nil;
if (self.sessionDidReceiveAuthenticationChallenge) {
//若是須要自定義處理當前的挑戰,那麼就經過block回調回去
disposition = self.sessionDidReceiveAuthenticationChallenge(session, challenge, &credential);
} else {
//挑戰憑證不必定都是進行HTTPS證書的信任,也多是須要客戶端提供用戶密碼或者提供雙向驗證時的客戶端證書。當這個挑戰憑證被驗證經過時,請求即可以繼續順利進行
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
//開始驗證證書 ,只須要驗證服務端證書是否安全(即https的單向認證,這是AF默認處理的認證方,其餘的認證方式,只能由咱們自定義Block的實現
if ([self.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) {
//驗證成功後,經過該證書建立一個NSURLCredential ,該證書會被傳給服務端
credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
//肯定挑戰方式
if (credential) {
//證書挑戰
disposition = NSURLSessionAuthChallengeUseCredential;
} else {
//默認挑戰
disposition = NSURLSessionAuthChallengePerformDefaultHandling;
}
} else {
//驗證失敗,代表客戶端沒辦法處理,那麼就Cancel掉
disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
}
} else {
//默認挑戰
disposition = NSURLSessionAuthChallengePerformDefaultHandling;
}
}
if (completionHandler) {
//將驗證完成的證書發給服務器
completionHandler(disposition, credential);
}
}
複製代碼
下面跳入真正作驗證的方法: 先來認識幾個屬性:網絡
typedef NS_ENUM(NSUInteger, AFSSLPinningMode) {
AFSSLPinningModeNone, //表明無條件信任服務器的證書,這個模式表示不作SSL pinning,只跟瀏覽器同樣在系統的信任機構列表裏驗證服務端返回的證書。若證書是信任機構簽發的就會經過,如果本身服務器生成的證書,這裏是不會經過的。
AFSSLPinningModePublicKey, //表明會對服務器返回的證書中的PublicKey進行驗證,客戶端要有服務端的證書拷貝,驗證時只驗證證書裏的公鑰,不驗證證書的有效期等信息。只要公鑰是正確的,就能保證通訊不會被竊聽,由於中間人沒有私鑰,沒法解開經過公鑰加密的數據。
AFSSLPinningModeCertificate, //表明會對服務器返回的證書同本地證書所有進行校驗,須要客戶端保存有服務端的證書拷貝,這裏驗證分兩步,第一步驗證證書的域名/有效期等信息,第二步是對比服務端返回的證書跟客戶端返回的是否一致。
};
複製代碼
上一步生成證書的方法爲:session
credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
//serverTrust爲SecTrustRef類型的數據,就是待驗證的對象。
複製代碼
- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust
forDomain:(NSString *)domain
{
//若是有域名 設置了容許信任無效或過時證書 須要驗證域名 沒有提供證書或者不驗證證書 返回NO
if (domain && self.allowInvalidCertificates && self.validatesDomainName && (self.SSLPinningMode == AFSSLPinningModeNone || [self.pinnedCertificates count] == 0)) {
return NO;
}
//用來裝驗證策略
NSMutableArray *policies = [NSMutableArray array];
//生成驗證策略。若是要驗證域名,就以域名爲參數建立一個策略,不然建立默認的basicX509策略
if (self.validatesDomainName) {
// 若是須要驗證domain,那麼就使用SecPolicyCreateSSL函數建立驗證策略,其中第一個參數爲true表示爲服務器證書驗證建立一個策略,第二個參數傳入domain,匹配主機名和證書上的主機名
//1.__bridge:CF和OC對象轉化時只涉及對象類型不涉及對象全部權的轉化
//2.__bridge_transfer:經常使用在講CF對象轉換成OC對象時,將CF對象的全部權交給OC對象,此時ARC就能自動管理該內存
//3.__bridge_retained:(與__bridge_transfer相反)經常使用在將OC對象轉換成CF對象時,將OC對象的全部權交給CF對象來管理
[policies addObject:(__bridge_transfer id)SecPolicyCreateSSL(true, (__bridge CFStringRef)domain)];
} else {
// 若是不須要驗證domain,就使用默認的BasicX509驗證策略
[policies addObject:(__bridge_transfer id)SecPolicyCreateBasicX509()];
}
// 爲serverTrust設置驗證策略
SecTrustSetPolicies(serverTrust, (__bridge CFArrayRef)policies);
if (self.SSLPinningMode == AFSSLPinningModeNone) {
//不作SSL pinning,只在系統的信任機構列表裏驗證服務端返回的證書
return self.allowInvalidCertificates || AFServerTrustIsValid(serverTrust);
} else if (!AFServerTrustIsValid(serverTrust) && !self.allowInvalidCertificates) {
//若是驗證無效AFServerTrustIsValid,並且allowInvalidCertificates不容許自籤,返回NO
return NO;
}
switch (self.SSLPinningMode) {
case AFSSLPinningModeNone:
default: //這裏上面已經判斷過了,執行到這裏的話直接返回NO
return NO;
//客戶端把服務端證書拷貝,存放在self.pinnedCertificates中
case AFSSLPinningModeCertificate: {
//所有校驗(nsbundle .cer)
NSMutableArray *pinnedCertificates = [NSMutableArray array];
//把證書data,用系統api轉成 SecCertificateRef 類型的數據,SecCertificateCreateWithData函數對原先的pinnedCertificates作一些處理,保證返回的證書都是DER編碼的X.509證書
for (NSData *certificateData in self.pinnedCertificates) {
[pinnedCertificates addObject:(__bridge_transfer id)SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certificateData)];
}
// 將pinnedCertificates設置成須要參與驗證的Anchor Certificate(錨點證書,經過SecTrustSetAnchorCertificates設置了參與校驗錨點證書以後,假如驗證的數字證書是這個錨點證書的子節點,即驗證的數字證書是由錨點證書對應CA或子CA簽發的,或是該證書自己,則信任該證書),具體就是調用SecTrustEvaluate來驗證
//serverTrust是服務器來的驗證,有須要被驗證的證書
// 把本地證書設置爲根證書,
SecTrustSetAnchorCertificates(serverTrust, (__bridge CFArrayRef)pinnedCertificates);
//再去驗證證書
if (!AFServerTrustIsValid(serverTrust)) {
return NO;
}
//注意,這個方法和咱們以前的錨點證書不要緊了,是去從咱們須要被驗證的服務端證書,去拿證書鏈。
// 服務器端的證書鏈,注意此處返回的證書鏈順序是從葉節點到根節點
// 全部服務器返回的證書信息
NSArray *serverCertificates = AFCertificateTrustChainForServerTrust(serverTrust);
for (NSData *trustChainCertificate in [serverCertificates reverseObjectEnumerator]) {
//若是咱們的證書中,有一個和它證書鏈中的證書匹配的,就返回YES
// 是否本地包含相同的data
if ([self.pinnedCertificates containsObject:trustChainCertificate]) {
return YES;
}
}
return NO;
}
case AFSSLPinningModePublicKey: {
//只驗證公鑰
NSUInteger trustedPublicKeyCount = 0;
// 從serverTrust中取出服務器端傳過來的全部可用的證書,並依次獲得相應的公鑰
NSArray *publicKeys = AFPublicKeyTrustChainForServerTrust(serverTrust);
//遍歷服務端公鑰
for (id trustChainPublicKey in publicKeys) {
for (id pinnedPublicKey in self.pinnedPublicKeys) {
if (AFSecKeyIsEqualToKey((__bridge SecKeyRef)trustChainPublicKey, (__bridge SecKeyRef)pinnedPublicKey)) {
//判斷若是相同 trustedPublicKeyCount+1
trustedPublicKeyCount += 1;
}
}
}
return trustedPublicKeyCount > 0;
}
}
return NO;
}
複製代碼