Ich versuche, HTTP Basic Auth mit NSURLSession
zu implementieren, aber ich stoße auf mehrere Probleme. Bitte lesen Sie die gesamte Frage, bevor Sie antworten, ich bezweifle, dass dies ein Duplikat einer anderen Frage ist.HTTP Basic Auth mit NSURLSession
Nach den Tests, die ich laufen habe, das Verhalten von NSURLSession
ist folgende:
- Die erste Anforderung wird immer gemacht, ohne die
Authorization
-Header. - Wenn die erste Anfrage mit einer
401 Unauthorized
Antwort und einemWWW-Authenticate Basic realm=...
Header fehlschlägt, wird es automatisch erneut versucht. - Bevor die Anfrage erneut versucht wird, versucht die Sitzung, Anmeldeinformationen zu erhalten, indem sie in die
NSURLCredentialStorage
der Sitzungskonfiguration oder durch den Aufruf der DelegiertenmethodeURLSession:task:didReceiveChallenge:completionHandler:
(oder beides) sucht. - Wenn Anmeldeinformationen erhalten werden können, wird die Anforderung mit dem richtigen Header
Authorization
wiederholt. Wenn nicht, wird es ohne den Header wiederholt (was seltsam ist, da es sich in diesem Fall genau um dieselbe Anfrage handelt). - Wenn die zweite Anforderung erfolgreich ist, wird die Aufgabe transparent als erfolgreich gemeldet und Sie werden nicht einmal benachrichtigt, dass die Anforderung zweimal versucht wurde. Wenn nicht, wird der Fehler der zweiten Anfrage gemeldet (aber nicht der erste).
Das Problem, das ich mit diesem Verhalten ist, dass ich große Dateien auf meinen Server über mehrteilige Anfragen bin das Hochladen, so, wenn die Anforderung zweimal versucht wird, der gesamte POST
Körper wird zweimal gesendet, die eine schreckliche Overhead ist.
Ich habe versucht, die Authorization
-Header an die httpAdditionalHeaders
der Sitzungskonfiguration manuell hinzufügen, aber es funktioniert nur, wenn die Eigenschaft festgelegt ist vor die Sitzung erstellt wird. Der Versuch, danach session.configuration.httpAdditionalHeaders
zu ändern, funktioniert nicht. Auch die Dokumentation sagt eindeutig, dass der Authorization
Header nicht manuell gesetzt werden sollte.
So ist meine Frage: Wenn ich die Sitzung starten müssen, bevor ich die Anmeldeinformationen erhalten, und wenn ich sicher sein wollen, dass die Anfragen werden immer mit dem richtigen Authorization
Header das erste Mal gemacht, wie kann ich machen ?
Hier ist ein Codebeispiel, das ich für meine Tests verwendet habe. Sie können alle oben beschriebenen Verhaltensweisen damit reproduzieren.
Beachten Sie, dass Sie Ihren eigenen HTTP-Server verwenden wil müssen entweder um in der Lage sein, die doppelten Anfragen zu sehen, und die Anfragen zu protokollieren oder ein Proxy-Verbindung über die alle Anforderungen protokolliert (I Charles Proxy dafür verwendet habe)
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)
}
}
}
Anmerkung 1: Wenn mehrere Anfragen in einer Reihe auf dem gleichen Endpunkt zu machen, habe ich das Verhalten oben genannten Bedenken die erste Anforderung nur beschrieben habe. Nachfolgende Anforderungen werden beim ersten Mal mit dem richtigen Header Authorization
ausprobiert. Wenn Sie jedoch einige Zeit warten (etwa 1 Minute), kehrt die Sitzung zum Standardverhalten zurück (die erste Anforderung wurde zweimal versucht).
Anmerkung 2: Dies ist nicht direkt verwandt, aber NSURLCredentialStorage
für die urlCredentialStorage
der Sitzungskonfiguration Verwendung eines benutzerdefinierten scheint nicht zu funktionieren. Verwenden Sie nur den Standardwert (der die gemeinsame NSURLCredentialStorage
gemäß der Dokumentation ist).
Anmerkung 3: Ich habe versucht, mit Alamofire
, aber da auf NSURLSession
basiert es, verhält es sich genau in der gleichen Art und Weise.
Warum nicht immer die erste Anfrage keine andere Arbeit als einen richtigen Authorization-Header bekommen? Die nächste Anfrage (und weiter) macht das ganze schwere Heben. – GlennRay
Versuchen Sie, nicht zu überschreiben 'didReceiveChallenge' – RunLoop
@GlennRay Das ist eine hässliche Lösung, die ich nicht einmal in Betracht ziehen möchte. Immer noch verbraucht Bandbreite ohne triftigen Grund und wie gesagt, es ist nicht nur die allererste Anfrage der Sitzung, sondern die erste Anfrage jedes Mal, wenn Sie etwa 1min bleiben, ohne etwas zu senden, so dass auch schrecklich unpraktisch ist, implementieren. – deadbeef