我試圖與NSURLSession
實現HTTP基本身份驗證,但我遇到了幾個問題。請在回答之前閱讀完整個問題,我懷疑這是其他問題的重複。與NSURLSession的HTTP基本身份驗證
據我已經運行測試,NSURLSession
行爲如下:
- 的第一個請求沒有
Authorization
頭總是讓。 - 如果第一個請求失敗並返回
401 Unauthorized
響應和WWW-Authenticate Basic realm=...
標頭,它將自動重試。 - 在重試請求之前,會話將嘗試通過查看會話配置的
NSURLCredentialStorage
或通過調用代理方法(或兩者)來獲取憑證。 - 如果可以獲取證書,則使用正確的
Authorization
標題重試請求。如果沒有,它會重試沒有標題(這很奇怪,因爲在這種情況下,這是完全相同的請求)。 - 如果第二個請求成功,則該任務將被透明地報告爲成功,並且您甚至不會通知該請求已嘗試兩次。如果不是,則報告第二個請求失敗(但不是第一個)。
我有這種行爲的問題是,我上傳大文件,通過多部分請求我的服務器,所以當請求嘗試兩次,整個POST
體發送兩次,這是一個可怕的開銷。
我試圖給Authorization
頭手動添加到會話配置的httpAdditionalHeaders
,但前提是該屬性設置在創建會話之前它的工作原理。試圖以後修改session.configuration.httpAdditionalHeaders
不起作用。此外,文檔中明確指出,不應手動設置標頭Authorization
。
所以我的問題是:如果我必須先獲得憑據啓動會話,如果我想以確保請求始終用正確的Authorization
頭在第一時間作出,我該怎麼辦做什麼?
這是我用於測試的代碼示例。你可以重現上面描述的所有行爲。
注意的是,爲了能夠看到你西港島線需要可以使用自己的HTTP服務器和日誌的請求或連接通過記錄所有請求的代理的雙重要求(我已經爲這個使用Charles Proxy)
class URLSessionTest: NSObject, URLSessionDelegate
{
static let shared = URLSessionTest()
func start()
{
let requestURL = URL(string: "https://httpbin.org/basic-auth/username/password")!
let credential = URLCredential(user: "username", password: "password", persistence: .forSession)
let protectionSpace = URLProtectionSpace(host: "httpbin.org", port: 443, protocol: NSURLProtectionSpaceHTTPS, realm: "Fake Realm", authenticationMethod: NSURLAuthenticationMethodHTTPBasic)
let useHTTPHeader = false
let useCredentials = true
let useCustomCredentialsStorage = false
let useDelegateMethods = true
let sessionConfiguration = URLSessionConfiguration.default
if (useHTTPHeader) {
let authData = "\(credential.user!):\(credential.password!)".data(using: .utf8)!
let authValue = "Basic " + authData.base64EncodedString()
sessionConfiguration.httpAdditionalHeaders = ["Authorization": authValue]
}
if (useCredentials) {
if (useCustomCredentialsStorage) {
let urlCredentialStorage = URLCredentialStorage()
urlCredentialStorage.set(credential, for: protectionSpace)
sessionConfiguration.urlCredentialStorage = urlCredentialStorage
} else {
sessionConfiguration.urlCredentialStorage?.set(credential, for: protectionSpace)
}
}
let delegate = useDelegateMethods ? self : nil
let session = URLSession(configuration: sessionConfiguration, delegate: delegate, delegateQueue: nil)
self.makeBasicAuthTest(url: requestURL, session: session) {
self.makeBasicAuthTest(url: requestURL, session: session) {
DispatchQueue.main.asyncAfter(deadline: .now() + 61.0) {
self.makeBasicAuthTest(url: requestURL, session: session) {}
}
}
}
}
func makeBasicAuthTest(url: URL, session: URLSession, completion: @escaping() -> Void)
{
let task = session.dataTask(with: url) { (data, response, error) in
if let response = response {
print("response : \(response)")
}
if let data = data {
if let json = try? JSONSerialization.jsonObject(with: data, options: .allowFragments) {
print("json : \(json)")
} else if data.count > 0, let string = String(data: data, encoding: .utf8) {
print("string : \(string)")
} else {
print("data : \(data)")
}
}
if let error = error {
print("error : \(error)")
}
print()
DispatchQueue.main.async(execute: completion)
}
task.resume()
}
@objc(URLSession:didReceiveChallenge:completionHandler:)
func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Swift.Void)
{
print("Session authenticationMethod: \(challenge.protectionSpace.authenticationMethod)")
if (challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodHTTPBasic) {
let credential = URLCredential(user: "username", password: "password", persistence: .forSession)
completionHandler(.useCredential, credential)
} else {
completionHandler(.performDefaultHandling, nil)
}
}
@objc(URLSession:task:didReceiveChallenge:completionHandler:)
func urlSession(_ session: URLSession, task: URLSessionTask, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Swift.Void)
{
print("Task authenticationMethod: \(challenge.protectionSpace.authenticationMethod)")
if (challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodHTTPBasic) {
let credential = URLCredential(user: "username", password: "password", persistence: .forSession)
completionHandler(.useCredential, credential)
} else {
completionHandler(.performDefaultHandling, nil)
}
}
}
注1:當進行連續多次請求到同一個端點,行爲上面我只關注第一個請求描述。隨後的請求首次嘗試使用正確的Authorization
標頭。但是,如果您等待一段時間(大約1分鐘),會話將返回到默認行爲(第一次請求嘗試兩次)。
注2:這是沒有直接關係,但使用自定義NSURLCredentialStorage
爲會話配置的urlCredentialStorage
似乎並沒有工作。只有使用默認值(根據文檔共享NSURLCredentialStorage
)纔有效。
注3:我已經使用Alamofire
試過,但由於它是基於NSURLSession
,它在完全相同的方式表現。
爲什麼沒有第一個請求總是沒有工作,除非得到一個適當的授權頭?接下來的請求(以及之後)完成所有繁重的工作。 – GlennRay
儘量不要重寫'didReceiveChallenge' – RunLoop
@GlennRay這是一個醜陋的解決方案,我甚至不想考慮。依然沒有充分的理由消耗帶寬,正如我所說的那樣,這不僅僅是會話的第一次請求,而是每次在沒有發送任何東西的情況下停留約1分鐘時的第一個請求,所以這也是非常不切實際的沒有實現。 – deadbeef