iOS 質(zhì)詢機(jī)制Authentication Challenge

在 iOS 中進(jìn)行網(wǎng)絡(luò)通信時(shí)捌治,為了安全,可能會(huì)產(chǎn)生認(rèn)證質(zhì)詢(Authentication Challenge)

場(chǎng)景

  • 當(dāng)遠(yuǎn)程服務(wù)器要求客戶證書或 Windows NT LAN Manager (NTLM) 驗(yàn)證時(shí),允許您的應(yīng)用程序提供適當(dāng)?shù)膽{證填具。
  • 當(dāng)一個(gè)會(huì)話首次建立與使用 SSLTLS 的遠(yuǎn)程服務(wù)器的連接時(shí)统舀,為了讓你的應(yīng)用程序驗(yàn)證服務(wù)器的證書鏈匆骗。

接收質(zhì)詢

在代碼需要向認(rèn)證的服務(wù)器請(qǐng)求資源時(shí),服務(wù)器會(huì)使用 http 狀態(tài)碼 401 進(jìn)行響應(yīng)誉简,即訪問被拒絕需要驗(yàn)證碉就。URLSession 會(huì)接收到響應(yīng)并在對(duì)應(yīng)的代理方法中處理質(zhì)詢。過程如下所示:


111.png

質(zhì)詢類型對(duì)應(yīng)的處理方法

222.png

session-level 代理方法

它是 URLSession 的代理方法

optional func urlSession(_ session: URLSession, 
didReceive challenge: URLAuthenticationChallenge, 
completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void)

non-session-level 代理方法

它是 URLSessionTask 的代理方法

optional func urlSession(_ session: URLSession, 
task: URLSessionTask, 
didReceive challenge: URLAuthenticationChallenge, 
completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void)

代理參數(shù)詳解

  • session: URLSession -> 當(dāng)前的會(huì)話對(duì)象
  • task: URLSessionTask -> 當(dāng)前的任務(wù)對(duì)象
  • challenge: URLAuthenticationChallenge -> 包含認(rèn)證請(qǐng)求的對(duì)象
  • completionHandler: (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) -> 處理完質(zhì)詢之后需要調(diào)用的回調(diào)
    • URLSession.AuthChallengeDisposition -> 如何處理質(zhì)詢
    • URLCredential -> 對(duì)應(yīng)質(zhì)詢類型的認(rèn)證憑證

注意:

  • 如果沒有實(shí)現(xiàn) URLSession 或者 URLSessionTask 的代理方法來正確的響應(yīng)挑戰(zhàn)闷串,那么就會(huì)收到 401(禁止)錯(cuò)誤瓮钥。
  • 如果沒有實(shí)現(xiàn) URLSession 的代理方法,session-level 的質(zhì)詢會(huì)走 URLSessionTask 的代理來處理烹吵,而 task-level 的質(zhì)詢不會(huì)通過 URLSession 的代理方法碉熄。

認(rèn)識(shí) URLAuthenticationChallenge、URLProtectionSpace肋拔、URLCredential锈津、URLSession.AuthChallengeDisposition

URLAuthenticationChallenge
class URLAuthenticationChallenge: NSObject {
    // 需要認(rèn)證的區(qū)域
    var protectionSpace: URLProtectionSpace
    // 表示最后一次認(rèn)證失敗的 URLResponse 實(shí)例
    var failureResponse: URLResponse?
    // 之前認(rèn)證失敗的次數(shù)
    var previousFailureCount: Int
    // 建議的憑據(jù),有可能是質(zhì)詢提供的默認(rèn)憑據(jù)凉蜂,也有可能是上次認(rèn)證失敗時(shí)使用的憑據(jù)
    var proposedCredential: URLCredential?
    // 上次認(rèn)證失敗的 Error 實(shí)例
    var error: Error?
    // 質(zhì)詢的發(fā)送者
    var sender: URLAuthenticationChallengeSender?
}

URLProtectionSpace

質(zhì)詢類型等各種信息都在 URLProtectionSpace 對(duì)象中
authenticationMethod 的值表示了質(zhì)詢的類型琼梆,根據(jù)這個(gè)值來決定我們?cè)趺错憫?yīng)挑戰(zhàn),具體類型見上文窿吩。

class URLProtectionSpace : NSObject {
    // 質(zhì)詢的類型
    var authenticationMethod: String
    // 進(jìn)行客戶端證書認(rèn)證時(shí)茎杂,可接受的證書頒發(fā)機(jī)構(gòu)
    var distinguishedNames: [Data]?
    var host: String
    var port: Int
    var `protocol`: String?
    var proxyType: String?
    var realm: String?
    var receivesCredentialSecurely: Bool
    // 表示服務(wù)器的SSL事務(wù)狀態(tài)
    var serverTrust: SecTrust?
}

URLCredential

成功響應(yīng)質(zhì)詢,還需要提供對(duì)應(yīng)的憑據(jù)纫雁。有三種初始化方式煌往,分別用于不同類型的質(zhì)詢類型。

// 使用給定的持久性設(shè)置先较、用戶名和密碼創(chuàng)建 URLCredential 實(shí)例携冤。
public init(user: String, password: String, persistence: URLCredential.Persistence) {
    
}

// 用于客戶端證書認(rèn)證質(zhì)詢,當(dāng) challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodClientCertificate 時(shí)使用
// identity: 私鑰和和證書的組合
// certArray: 大多數(shù)情況下傳 nil
// persistence: 該參數(shù)會(huì)被忽略闲勺,傳 .forSession 會(huì)比較合適
public init(identity: SecIdentity, certificates certArray: [Any]?, persistence: URLCredential.Persistence) {
    
}

// 用于服務(wù)器信任認(rèn)證質(zhì)詢曾棕,當(dāng) challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust 時(shí)使用
// 從 challenge.protectionSpace.serverTrust 中獲取 SecTrust 實(shí)例
// 使用該方法初始化 URLCredential 實(shí)例之前,需要對(duì) SecTrust 實(shí)例進(jìn)行評(píng)估
public init(trust: SecTrust) {
    
}

URLCredential.Persistence

用于表明 URLCredential 實(shí)例的持久化方式菜循,只有基于用戶名和密碼創(chuàng)建的 URLCredential 實(shí)例才會(huì)被持久化到 keychain 里面

public enum Persistence : UInt {

    case none
    case forSession
    // 會(huì)存儲(chǔ)在 iOS 的 keychain 里面
    case permanent
    // 會(huì)存儲(chǔ)在 iOS 的 keychain 里面翘地,并且會(huì)通過 iCloud 同步到其他 iOS 設(shè)備
    @available(iOS 6.0, *)
    case synchronizable
}
URLSession.AuthChallengeDisposition
public enum AuthChallengeDisposition : Int {

    // 使用指定的憑據(jù)(credential)
    case useCredential 

    // 默認(rèn)的質(zhì)詢處理,如果有提供憑據(jù)也會(huì)被忽略癌幕,如果沒有實(shí)現(xiàn) URLSessionDelegate 處理質(zhì)詢的方法則會(huì)使用這種方式
    case performDefaultHandling 
    
    // 取消認(rèn)證質(zhì)詢衙耕,如果有提供憑據(jù)也會(huì)被忽略,會(huì)取消當(dāng)前的 URLSessionTask 請(qǐng)求
    case cancelAuthenticationChallenge 

    // 拒絕質(zhì)詢勺远,并且進(jìn)行下一個(gè)認(rèn)證質(zhì)詢橙喘,如果有提供憑據(jù)也會(huì)被忽略;大多數(shù)情況不會(huì)使用這種方式胶逢,無法為某個(gè)質(zhì)詢提供憑據(jù)厅瞎,則通常應(yīng)返回 performDefaultHandling
    case rejectProtectionSpace
}


如何響應(yīng)質(zhì)詢

兩個(gè)接收質(zhì)詢的代理方法都有 session, challenge, 以及一個(gè) completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void 閉包參數(shù)饰潜。

這個(gè)閉包接受兩個(gè)參數(shù),它們的類型分別為 URLSession.AuthChallengeDisposition 和簸、 URLCredential? 彭雾,需要根據(jù) challenge.protectionSpace.authenticationMethod 的值,確定如何響應(yīng)質(zhì)詢锁保,并且提供對(duì)應(yīng)的 URLCredential 實(shí)例

注意:
如果實(shí)現(xiàn)了兩個(gè)代理方法薯酝,執(zhí)行完自己的認(rèn)證邏輯之后,必須調(diào)用這個(gè)閉包來響應(yīng)質(zhì)詢爽柒,否則 NSURLSessionTask 會(huì)一直等待吴菠,既不會(huì)成功也不會(huì)失敗。

1 non-session-level

1.1 HTTP Basic

客戶端 -> 發(fā)送請(qǐng)求
服務(wù)器 -> 返回狀態(tài)碼 401 告訴客戶端需要認(rèn)證
客戶端 -> 用戶名和密碼 Base64 方式編碼后發(fā)送
服務(wù)器 -> 認(rèn)證成功返回 200霉赡,否則 401

1.2 HTTP Digest

客戶端 -> 發(fā)送請(qǐng)求
服務(wù)器 -> 返回狀態(tài)碼 401 及臨時(shí)的質(zhì)詢碼(隨機(jī)數(shù))
客戶端 -> 發(fā)送摘要以及由質(zhì)詢碼計(jì)算出的響應(yīng)碼
服務(wù)器 -> 認(rèn)證成功返回 200橄务,否則 401

1.3 HTMLForm

網(wǎng)上找的資料說,URLSession 不會(huì)觸發(fā)此類質(zhì)詢

1.4 iOS 實(shí)際代碼中如何處理

HTTP Basic 穴亏、 HTTP Digest 蜂挪、 NTLM 都是基于用戶名/密碼的認(rèn)證,處理這種認(rèn)證質(zhì)詢的

NTLM 屬于 session-level嗓化,Negotiate 實(shí)際上也是 NTLM棠涮,寫在這里方便大家閱讀

  func urlSession(_ session: URLSession, task: URLSessionTask, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
        switch challenge.protectionSpace.authenticationMethod {
        case NSURLAuthenticationMethodHTTPBasic, NSURLAuthenticationMethodHTTPDigest, NSURLAuthenticationMethodNTLM, NSURLAuthenticationMethodNegotiate:
            let user = "user"
            let password = "password"
            let credential = URLCredential(user: user, password: password, persistence: .forSession)
            completionHandler(.useCredential, credential)
        default:
            completionHandler(.performDefaultHandling, nil)
        }
    }
    

2 session-level

2.1 NSURLAuthenticationMethodClientCertificate

2.2 HTTPS Server Trust Authentication

大多數(shù)情況下,對(duì)于這種類型的認(rèn)證質(zhì)詢可以不實(shí)現(xiàn) URLSessionDelegate 處理認(rèn)證質(zhì)詢的方法刺覆, URLSessionTask 會(huì)使用默認(rèn)的處理方式( performDefaultHandling )進(jìn)行處理严肪。但是如果是以下的情況,則需要手動(dòng)進(jìn)行處理:

  • 與使用自簽名證書的服務(wù)器進(jìn)行 HTTPS 連接谦屑。
  • 進(jìn)行更嚴(yán)格的服務(wù)器信任評(píng)估來加強(qiáng)安全性驳糯,如:通過使用 SSL Pinning 來防止中間人攻擊。

2.2.1 處理權(quán)威機(jī)構(gòu)簽發(fā)的證書

對(duì)于權(quán)威機(jī)構(gòu)簽發(fā)的證書, 這類證書上面會(huì)聲明自己是由哪一個(gè)CA機(jī)構(gòu)(或CA的子機(jī)構(gòu))簽發(fā), 而對(duì)應(yīng)的CA機(jī)構(gòu)也有自己的CA證書, 在手機(jī)出廠之前就被安裝進(jìn)系統(tǒng)里了, 這樣對(duì)于權(quán)威機(jī)構(gòu)簽發(fā)的服務(wù)器證書, 只要從系統(tǒng)里找一下服務(wù)器證書對(duì)應(yīng)的CA證書, 拿CA證書的公鑰解密一下服務(wù)器證書的簽名, 解密出的Hash是不是和服務(wù)器攜帶的數(shù)據(jù)部分運(yùn)算出的Hash一致, 即可證明服務(wù)器證書是合法的. 如果不實(shí)現(xiàn)didReceiveChallenge這個(gè)協(xié)議方法, 系統(tǒng)會(huì)自動(dòng)幫忙處理好.

func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
    // 判斷認(rèn)證質(zhì)詢的類型氢橙,判斷是否存在服務(wù)器信任實(shí)例 serverTrust
    guard challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust,
        let serverTrust = challenge.protectionSpace.serverTrust else {
            // 否則使用默認(rèn)處理
            completionHandler(.performDefaultHandling, nil)
            return
    }
    // 自定義方法酝枢,對(duì)服務(wù)器信任實(shí)例 serverTrust 進(jìn)行評(píng)估
    if evaluate(trust, forHost: challenge.protectionSpace.host) {
        // 評(píng)估通過則創(chuàng)建 URLCredential 實(shí)例,告訴系統(tǒng)接受服務(wù)器的憑據(jù)
        let credential = URLCredential(trust: serverTrust)
        completionHandler(.useCredential, credential)
    } else {
        // 否則取消這次認(rèn)證悍手,告訴系統(tǒng)拒絕服務(wù)器的憑據(jù)
        completionHandler(.cancelAuthenticationChallenge, nil)
    }
}
  func evaluate(serverTrust: SecTrust, forHost: String) -> Bool {
        var trust : Bool = false
        if #available(iOS 12, *) {
            var error: CFError?
            trust = SecTrustEvaluateWithError(serverTrust, &error)
        } else {
            var result = SecTrustResultType.invalid
            let status = SecTrustEvaluate(serverTrust, &result)
            trust = (status == errSecSuccess && (result == .unspecified || result == .proceed))
        }
        
        return trust
    }
   

2.2.2 自簽名證書

比如 charles 或者各種抓包軟件帘睦,實(shí)際上他們就是自簽證書,
自簽名的證書是過不了系統(tǒng)的證書驗(yàn)證的坦康,如果服務(wù)器用了自簽名證書竣付,還想正常的訪問的話,需要把自簽證書添加到鑰匙串并信任滞欠,或者做自簽名證書的客戶端驗(yàn)證

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末古胆,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子筛璧,更是在濱河造成了極大的恐慌逸绎,老刑警劉巖川队,帶你破解...
    沈念sama閱讀 216,496評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件氓仲,死亡現(xiàn)場(chǎng)離奇詭異碾盐,居然都是意外死亡严沥,警方通過查閱死者的電腦和手機(jī)岗喉,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門赴肚,熙熙樓的掌柜王于貴愁眉苦臉地迎上來锈颗,“玉大人已亥,你說我怎么就攤上這事采蚀∑G#” “怎么了?”我有些...
    開封第一講書人閱讀 162,632評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵榆鼠,是天一觀的道長(zhǎng)纲爸。 經(jīng)常有香客問我,道長(zhǎng)妆够,這世上最難降的妖魔是什么识啦? 我笑而不...
    開封第一講書人閱讀 58,180評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮神妹,結(jié)果婚禮上颓哮,老公的妹妹穿的比我還像新娘。我一直安慰自己鸵荠,他們只是感情好冕茅,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,198評(píng)論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著蛹找,像睡著了一般姨伤。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上庸疾,一...
    開封第一講書人閱讀 51,165評(píng)論 1 299
  • 那天乍楚,我揣著相機(jī)與錄音,去河邊找鬼彼硫。 笑死炊豪,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的拧篮。 我是一名探鬼主播词渤,決...
    沈念sama閱讀 40,052評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼串绩!你這毒婦竟也來了缺虐?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,910評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤礁凡,失蹤者是張志新(化名)和其女友劉穎高氮,沒想到半個(gè)月后慧妄,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,324評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡剪芍,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,542評(píng)論 2 332
  • 正文 我和宋清朗相戀三年塞淹,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片罪裹。...
    茶點(diǎn)故事閱讀 39,711評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡饱普,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出状共,到底是詐尸還是另有隱情套耕,我是刑警寧澤,帶...
    沈念sama閱讀 35,424評(píng)論 5 343
  • 正文 年R本政府宣布峡继,位于F島的核電站冯袍,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏碾牌。R本人自食惡果不足惜康愤,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,017評(píng)論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望舶吗。 院中可真熱鬧翘瓮,春花似錦、人聲如沸裤翩。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)踊赠。三九已至呵扛,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間筐带,已是汗流浹背今穿。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留伦籍,地道東北人蓝晒。 一個(gè)月前我還...
    沈念sama閱讀 47,722評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像帖鸦,于是被迫代替她去往敵國(guó)和親芝薇。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,611評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容

  • 概述 在 iOS 中進(jìn)行網(wǎng)絡(luò)通信時(shí)作儿,為了安全洛二,可能會(huì)產(chǎn)生認(rèn)證質(zhì)詢(Authentication Challenge...
    Dan1els閱讀 1,630評(píng)論 0 3
  • 原創(chuàng):知識(shí)點(diǎn)總結(jié)性文章創(chuàng)作不易,請(qǐng)珍惜,之后會(huì)持續(xù)更新晾嘶,不斷完善個(gè)人比較喜歡做筆記和寫總結(jié)妓雾,畢竟好記性不如爛筆頭哈...
    時(shí)光啊混蛋_97boy閱讀 2,842評(píng)論 0 4
  • 即將離開簡(jiǎn)書,請(qǐng)到掘金繼續(xù)關(guān)注我垒迂。謝謝械姻! 本文掘金鏈接[https://juejin.cn/post/687514...
    Lebron_James閱讀 7,823評(píng)論 16 15
  • 通過Authentication Challenge來信任自簽名Https證書[https://www.cnblo...
    介和閱讀 390評(píng)論 0 1
  • 即將離開簡(jiǎn)書,請(qǐng)到掘金繼續(xù)關(guān)注我机断。謝謝策添! 本文掘金鏈接[https://juejin.cn/post/687514...
    Lebron_James閱讀 43,796評(píng)論 6 87