我們在用NSURLConnection或者NSURLSession進行HTTP請求時,有些URL因為需要授權(quán)認(rèn)證而返回401,因此客戶端需要在HTTP的請求頭中帶上用戶和密碼進行授權(quán)認(rèn)證(具體查看這里);或者當(dāng)我們使用HTTPS協(xié)議時,一旦服務(wù)器提供的證書不被默認(rèn)信任則需要客戶端人為確認(rèn)是否信任此服務(wù)器證書;或者用HTTPS協(xié)議時服務(wù)端也需要客戶端提供證書進行雙向認(rèn)證時;或者我們是通過代理服務(wù)器來請求數(shù)據(jù)時客戶端需要提供代理服務(wù)器的用戶和密碼進行認(rèn)證。我們稱這些情況為服務(wù)端要求客戶端接收認(rèn)證挑戰(zhàn)(AuthenticationChallenge)。
當(dāng)我們使用NSURLConnection來請求需要挑戰(zhàn)的頁面的時delegate會先調(diào)用協(xié)議函數(shù):
(void)connection:(NSURLConnection*)connectionwillSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge*)challenge。
也就是如果有需要認(rèn)證時不會先調(diào)用didReceiveResponse,而是先調(diào)用上面的函數(shù),下面介紹NSURLAuthenticationChallenge類,這個類是認(rèn)證挑戰(zhàn)類,也就是要求客戶端進行挑戰(zhàn),要接收挑戰(zhàn)也就是客戶端提供挑戰(zhàn)的憑證(用戶和密碼,或者客戶端證書,或者信任服務(wù)器證書,或者代理),IOS提供了一個NSURLCredential的類來表示挑戰(zhàn)憑證??梢酝ㄟ^如下函數(shù)來建立挑戰(zhàn)憑證
//通過用戶密碼建立憑證,這種用于401錯誤的挑戰(zhàn)憑證和代理的挑戰(zhàn)憑證
(id)initWithUser:(NSString*)user password:(NSString *)passwordpersistence:(NSURLCredentialPersistence)persistence;
//這種是要求客戶端提供證書來建立的挑戰(zhàn)憑證,用于服務(wù)器要認(rèn)證客戶端的情況,我們需要從鑰匙串中得到一個客戶端證書。
(id)initWithIdentity:(SecIdentityRef)identitycertificates:(NSArray *)certArraypersistence:(NSURLCredentialPersistence) persistence
//這種要求客戶端的對服務(wù)器的信任來建立憑證,所謂SecTrust用來描述信任某個證書用來做什么的東西,比如一個證書可以用來做SSL,用來做簽名,郵件安全(這個證書以及可以用來做什么來構(gòu)造一個信任)
-(id)initWithTrust:(SecTrustRef)trustNS_AVAILABLE(10_6,3_0);
上面的2中證書中都有一個persistence值,這是一個枚舉值用來描述這個憑證的保存策略,一共有三種:
- NSURLCredentialPersistenceNone, //不保存,只請求一次。
- NSURLCredentialPersistenceForSession, //只在本次會話中有效
- NSURLCredentialPersistencePermanent //永久有效,保存在鑰匙串中,其他也有效
為什么服務(wù)器信任的憑證不需要保存到存儲中,原因是服務(wù)器信任的憑證總是從服務(wù)器下發(fā)給客戶端的
為什么要有保存策略呢?想想如果我們不保存的話我們每次都要進行用戶手動處理太麻煩了,因此系統(tǒng)提供了一個地方來保存這些憑證,這樣我們的挑戰(zhàn)對象NSURLAuthenticationChallenge就可以根據(jù)特殊的信息(后面說明)來獲取這些憑證而不必要每次都需要手動處理,這個保存的地方叫做NSURLCredentialStorage是一個憑證存儲類,這個類提供一個單例的方法來訪問憑證存儲對象。我們知道一個挑戰(zhàn)是服務(wù)器端向客戶端發(fā)起的,所以提供一個憑證是根據(jù)服務(wù)端要求的信息來建立的,我們的挑戰(zhàn)類NSURLAuthenticationChallenge根據(jù)服務(wù)端要求的挑戰(zhàn)方式,從憑證存儲類NSURLCredentialStorage中獲取一個具體的憑證對象,然后接收挑戰(zhàn)。而服務(wù)器要求的挑戰(zhàn)方式我們用一個類NSURLProtectionSpace叫做保護空間來描述。那么保護空間里面有哪些信息呢?可以肯定的是包括挑戰(zhàn)的方式(401授權(quán),客戶端證書,服務(wù)端要求信任等,如果是這個則會提供一個SecTrust對象)、服務(wù)器的URL地址,端口號,協(xié)議等等。確實如此,一個NSURLProtectionSpace提供如下信息:
//401的認(rèn)證方式的realm字段的值
(NSString*)realm;
//401的認(rèn)證方式,指定是否密碼發(fā)送安全。
-(BOOL)receivesCredentialSecurely;
//代理授權(quán)
-(BOOL)isProxy;
//服務(wù)端主機地址,如果是代理則代理服務(wù)器地址
-(NSString *)host;
//服務(wù)端端口地址,如果是代理則代理服務(wù)器的端口
-(NSInteger)port;
//代理類型,只對代理授權(quán),比如http代理,socket代理等。
-(NSString *)proxyType;
//使用的協(xié)議,比如http,https, ftp等,
-(NSString *)protocol;
//最關(guān)鍵字段,指定授權(quán)方式,比如401,客戶端認(rèn)證,服務(wù)端信任,代理等。
-(NSString *)authenticationMethod;
//客戶端認(rèn)證,是指定可接受的客戶端證書列表??表示只有這些證書才可以??
-(NSArray *)distinguishedNames NS_AVAILABLE(10_6,3_0);
//用于服務(wù)端信任,指定一個信任對象,可以用這個對象來建立一個憑證。
-(SecTrustRef)serverTrust NS_AVAILABLE(10_6,3_0);
保護空間的建立提供2個方法:
(id)initWithHost:(NSString*)host port:(NSInteger)port protocol:(NSString *)protocolrealm:(NSString *)realm authenticationMethod:(NSString*)authenticationMethod;
//代理的保護空間
-(id)initWithProxyHost:(NSString*)host port:(NSInteger)port type:(NSString *)type realm:(NSString*)realm authenticationMethod:(NSString *)authenticationMethod;
好了有了保護空間類,也憑證類我們就可以把信息從憑證空間讀取或者保存了,憑證空間提供了如下的方法:
//根據(jù)保護空間得到憑證對象字典,這個字典的key是用戶名,而value是NSURLCredential
-(NSDictionary *)credentialsForProtectionSpace:(NSURLProtectionSpace*)space;
//所有憑證對象字典,key是保護空間,value是一個字典,其中value的key是用戶名字,value是憑證
-(NSDictionary *)allCredentials;
//保存憑證
-(void)setCredential:(NSURLCredential*)credential forProtectionSpace:(NSURLProtectionSpace *)space;
//刪除憑證
-(void)removeCredential:(NSURLCredential*)credential forProtectionSpace:(NSURLProtectionSpace *)space;
//設(shè)置某個保護空間的默認(rèn)憑證
-(NSURLCredential*)defaultCredentialForProtectionSpace:(NSURLProtectionSpace *)space;
//獲取某個憑證空間的默認(rèn)憑證
-(void)setDefaultCredential:(NSURLCredential*)credential forProtectionSpace:(NSURLProtectionSpace *)space;
當(dāng)我們的憑證存儲空間有變化時會發(fā)送FOUNDATION_EXPORTNSString *constNSURLCredentialStorageChangedNotification;通知。
好了說了這么多,然我們來繼續(xù)看看挑戰(zhàn)類吧NSURLAuthenticationChallenge,一個挑戰(zhàn)類會包含:保護空間信息,憑證類(如果有的話),
這個類的函數(shù)如下:
//這個函數(shù)返回一個類NSURLProtectionSpace,類中描述服務(wù)器中希望的認(rèn)證方式以及協(xié)議,主機端口號等信息。
(NSURLProtectionSpace*)protectionSpace;
//上次客戶端接收挑戰(zhàn)時所指定的認(rèn)證的憑證,在沒有指定時默認(rèn)為nil
-(NSURLCredential*)proposedCredential;
//用戶密碼輸入失敗的重復(fù)次數(shù)。
-(NSInteger)previousFailureCount;
//也就是一個401響應(yīng)頭的詳細(xì)信息。
-(NSURLResponse*)failureResponse;
//錯誤信息
-(NSError*)error;
//這個方法用來指定客戶端如何來接收挑戰(zhàn)。也就是客戶端在處理willSendRequestForAuthenticationChallenge函數(shù)的最后必須指定接收挑戰(zhàn)的方式??蛻舳丝梢哉{(diào)用sender中的協(xié)議指定的方法來執(zhí)行接收挑戰(zhàn)的方式。這個sender是系統(tǒng)實現(xiàn)的,客戶端只要調(diào)用就可以了。
(id<NSURLAuthenticationChallengeSender>)sender;
//上面的sender是我們需要告訴服務(wù)器我們?nèi)绾蝸斫邮芴魬?zhàn),這個協(xié)議實現(xiàn)了如下函數(shù):
@protocolNSURLAuthenticationChallengeSender <NSObject>
//如果用戶接收挑戰(zhàn)則需要用戶提供一個授權(quán)的憑證credential,用戶需要建立這個憑證NSURLCredential對象。
-(void)useCredential:(NSURLCredential*)credential forAuthenticationChallenge:(NSURLAuthenticationChallenge*)challenge;
//告訴服務(wù)器我不管他要認(rèn)證我繼續(xù)處理不用輸入認(rèn)證用戶和密碼,如果調(diào)用了這個函數(shù)則會調(diào)用URLConnection的delegate的didReceiveResponse函數(shù)并且響應(yīng)為401
-(void)continueWithoutCredentialForAuthenticationChallenge:(NSURLAuthenticationChallenge*)challenge;
//如果調(diào)用這個函數(shù)則我表示我不接收挑戰(zhàn)了,這時候會調(diào)用URLConnection的delegate的didFailWithError函數(shù)表示錯誤了。
(void)cancelAuthenticationChallenge:(NSURLAuthenticationChallenge*)challenge;
@end
我們再來捋順一下邏輯,當(dāng)我們發(fā)送請求到服務(wù)端時,服務(wù)端需要我們挑戰(zhàn)時會在客戶端創(chuàng)建一個挑戰(zhàn)對象NSURLAuthenticationChallenge,其中的保護空間NSURLProtectionSpace由服務(wù)器響應(yīng)的信息來構(gòu)建,而<NSURLAuthenticationChallengeSender>sender則內(nèi)部構(gòu)建,然后挑戰(zhàn)對象會根據(jù)保護空間從憑證存儲中獲取對應(yīng)的憑證對象,如果有憑證對象則會把憑證對象賦值給數(shù)據(jù)成員proposedCredential,建立挑戰(zhàn)對象后判斷當(dāng)前有沒有實現(xiàn)NSURLConnection的willSendRequestForAuthenticationChallenge的函數(shù),如果沒有實現(xiàn)則根據(jù)憑證對象來調(diào)用sender的接受挑戰(zhàn)或者失敗函數(shù),而如果是我們實現(xiàn)了willSendRequestForAuthenticationChallenge就需要我們自己來處理如何接收挑戰(zhàn)了,注意當(dāng)我們調(diào)用sender的接收挑戰(zhàn)函數(shù),這個函數(shù)內(nèi)部會把憑證和保護空間保存到憑證存儲中去,以便下次繼續(xù)使用(當(dāng)然可以通過控制憑證的持久屬性來決定是否保存)。因此有的時候我們可以在系統(tǒng)中預(yù)先植入一些特定服務(wù)器的保護空間和憑證,這樣我們就不需要去處理willSendRequestForAuthenticationChallenge函數(shù)了,這種機制特別有效的用于處理webview來訪問有些需要授權(quán)的或者h(yuǎn)ttps或者代理等等。