一個NSURLRequest物件經常會遇到認證請求,或者需要從其所連線的服務端請求證書。當需要認證請求時,NSURLConnection、NSURLSession和NSURLDownload類會通知它們的代理物件,以便能正確地做處理。不過需要注意的是,URL載入系統只有在服務端響應包含WWW-Authenticate頭時才會呼叫代理來處理認證請求,而類似於代理認證和TLS信任驗證這樣的認證型別則不需要這個頭。
確定如何響應一個認證請求
如果一個NSURLRequest物件需要認證時,則認證請求方式取決於使用的物件的型別:
如果請求是與NSURLSession物件關聯,則所有認證請求都會傳遞給代理,而不考慮認證的型別。
如果請求是與NSURLConnection或NSURLDownload物件,則物件的代理接收一個connection:canAuthenticateAgainstProtectionSpace: (或者 download:canAuthenticateAgainstProtectionSpace:) 訊息。這允許代理物件在嘗試再次認證前分析服務端的屬性,包括協議和認證方法。如果我們的代理物件不準備認證服務端的受保護空間,則返回NO,且系統嘗試使用使用者的keychain的資訊進行認證。
如果NSURLConnection或NSURLDownload的代理物件沒有實現connection:canAuthenticateAgainstProtectionSpace: (或者 download:canAuthenticateAgainstProtectionSpace:)方法,且保護空間使用客戶端證書認證或服務端信任認證,則系統假設我們返回NO。而物件其它所有型別,系統都返回YES。
下一步,如果我們的代理物件同意處理認證,但是沒有有效的證書(不管是作為請求URL的一部分或者在NSURLCredentialStorage中共享),則代理以收到以下訊息:
URLSession:didReceiveChallenge:completionHandler:2. URLSession:task:didReceiveChallenge:completionHandler:3. connection:didReceiveAuthenticationChallenge:4. download:didReceiveAuthenticationChallenge:
為了讓連線能夠繼續,則代理物件有三種選擇:
提供認證證書
嘗試在沒有證書的情況下繼續
取消認證查詢
為了確保操作的正確流程,傳遞給這些方法的NSURLAuthenticationChallenge例項會包含一些資訊,包括是什麼觸發了認證查詢、查詢的嘗試次數、任何先前嘗試的證書、請求證書的NSURLProtectionSpace物件,及查詢的傳送者。
如果認證請求事先嚐試認證且失敗了(如使用者在服務端修改了密碼),我們可以通過在認證請求呼叫proposedCredential來獲取嘗試憑據。代理可以使用這些證書來填充一個顯示給使用者的話框。
呼叫認證請求的previousFailureCount可以返回身份驗證嘗試次數,這些嘗試包括不同認證協議的嘗試請求。代理可以將這些方法提供給使用者,以確定先前提供的證書是否失敗,或限制最大認證嘗試次數。
響應認證請求
前面說過三種響應我們響應connection:didReceiveAuthenticationChallenge:代理方法的方式,我們將逐一介紹:
提供證書
為了進行認證,程式需要使用服務端期望的認證資訊建立一個NSURLCredential物件。我們可以呼叫authenticationMethod來確定服務端的認證方法,這個認證方法是在提供的認證請求的保護空間中。NSURLCredential支援一些方法:
HTTP基本認證(NSURLAuthenticationMethodHTTPBasic):需要使用者名稱和密碼。提示使用者輸入必要資訊並使用credentialWithUser:password:persistence:方法建立一個NSURLCredential物件。
HTTP數字認證(NSURLAuthenticationMethodHTTPDigest):類似於基本認證,需要使用者名稱和密碼。提示使用者輸入必要資訊並使用credentialWithUser:password:persistence:方法建立一個NSURLCredential物件。
客戶端證書認證(NSURLAuthenticationMethodClientCertificate): 需要系統標識和所有服務端認證所需要的證書。然後使用credentialWithIdentity:certificates:persistence:來建立一個NSURLCredential物件。
服務端信任認證(NSURLAuthenticationMethodServerTrust)需要一個由認證請求的保護空間提供的信任。使用credentialForTrust:來建立一個NSURLCredential物件。
在建立NSURLCredential物件後
對於NSURLSession,使用提供的完成處理block將該物件傳遞給認證請求傳送者
對於NSURLConnection和NSURLDownload,使用useCredential:forAuthenticationChallenge:方法將物件傳遞給認證請求傳送者。
嘗試在沒有證書的情況下繼續
如果代理選擇不提供證書,可以嘗試繼續操作:
對於NSURLSession,傳遞下面的值給完成處理block: NSURLSessionAuthChallengePerformDefaultHandling:處理請求。儘管代理沒有提供代理方法來處理認證請求 NSURLSessionAuthChallengeRejectProtectionSpace:拒絕請求。依賴於服務端響應允許的認證型別,URL載入類可能多次呼叫這個代理方法。
對於NSURLConnection和NSURLDownload,在[challenge sender]中呼叫continueWithoutCredentialsForAuthenticationChallenge:。
依賴於協議的實現,這種處理方法可能會導致連線失敗而以送connectionDidFailWithError:訊息,或者返回可選的不需要認證的URL內容。
取消連線
代理可以選擇取消認證請求
對於NSURLSession,傳遞NSURLSessionAuthChallengeCancelAuthenticationChallenge給完成處理block
對於NSURLConnection和NSURLDownload,在[challenge sender]中呼叫cancelAuthenticationChallenge:。代理接收connection:didCancelAuthenticationChallenge:訊息,以提供使用者反饋的機會。
下面的程式碼演示了使用使用者名稱和密碼建立NSURLCredential物件來響應認證請求
-(void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
if ([challenge previousFailureCount] == 0)
{
NSURLCredential *newCredential;
newCredential = [NSURLCredential credentialWithUser:[self preferencesName] password:[self preferencesPassword] persistence:NSURLCredentialPersistenceNone];
[[challenge sender] useCredential:newCredential forAuthenticationChallenge:challenge];
}
else
{ [[challenge sender] cancelAuthenticationChallenge:challenge]; // inform the user that the user name and password // in the preferences are incorrect [self showPreferencesCredentialsAreIncorrectPanel:self]; }
}
如果代理沒有實現connection:didReceiveAuthenticationChallenge:,而請求需要認證,則有效的證書必須位於URL證書儲存中或作為請求URL的一部分。如果證書無效或者認證失敗,則底層實現會傳送一個continueWithoutCredentialForAuthenticationChallenge:訊息。
執行自定義TLS鏈驗證
在NSURL系統的AIP中,TLS鏈驗證由應用的認證代理方法來處理,但它不是提供證書給服務端以驗證使用者,而是在TLS握手的過程中校驗服務端提供的證書,然後再告訴URL載入系統是否應該接受還是拒絕這些證書。
如果需要以非標準的方法(如接收一個指定的自標識的證書用於測試)來執行鏈驗證,則應用必須如下處理:
對於NSURLSession,實現URLSession:didReceiveChallenge:completionHandler:和URLSession:task:didReceiveChallenge:completionHandler:代理方法。如果實現了兩者,由會話級別的方法負責處理認證。
對於NSURLConnection和NSURLDownload,實現connection:canAuthenticateAgainstProtectionSpace:和download:canAuthenticateAgainstProtectionSpace:方法,如果保護空間有一個NSURLAuthenticationMethodServerTrust型別的認證,則返回YES。然後,實現connection:didReceiveAuthenticationChallenge:或download:didReceiveAuthenticationChallenge:方法來處理認證。
在認證處理代理方法中,我們需要確認認證保護空間是否有NSURLAuthenticationMethodServerTrust型別的認證,如果有,則從保護空間獲取serverTrust資訊。