Warning: file_get_contents(/data/phpspider/zhask/data//catemap/8/http/4.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Ios 使用NSURLSession的HTTP基本身份验证_Ios_Http_Nsurlsession_Basic Authentication_Urlsession - Fatal编程技术网

Ios 使用NSURLSession的HTTP基本身份验证

Ios 使用NSURLSession的HTTP基本身份验证,ios,http,nsurlsession,basic-authentication,urlsession,Ios,Http,Nsurlsession,Basic Authentication,Urlsession,我试图用NSURLSession实现HTTP基本身份验证,但遇到了几个问题。在回答之前,请阅读整个问题,我怀疑这是与另一个问题的重复 根据我运行的测试,NSURLSession的行为如下: 第一个请求总是在没有授权标题的情况下发出 如果第一个请求失败,出现401未经授权的响应和WWW-Authenticate-Basic-realm=…头,则会自动重试 在重试请求之前,会话将通过查看会话配置的NSURLCredentialStorage或调用URLSession:task:didReceive

我试图用
NSURLSession
实现HTTP基本身份验证,但遇到了几个问题。在回答之前,请阅读整个问题,我怀疑这是与另一个问题的重复

根据我运行的测试,
NSURLSession
的行为如下:

  • 第一个请求总是在没有
    授权
    标题的情况下发出
  • 如果第一个请求失败,出现
    401未经授权的
    响应和
    WWW-Authenticate-Basic-realm=…
    头,则会自动重试
  • 在重试请求之前,会话将通过查看会话配置的
    NSURLCredentialStorage
    或调用
    URLSession:task:didReceiveChallenge:completionHandler:
    委托方法(或两者)尝试获取凭据
  • 如果可以获得凭据,则使用正确的
    授权
    标头重试请求。如果没有,则在没有标头的情况下重试(这很奇怪,因为在本例中,这是完全相同的请求)
  • 如果第二个请求成功,任务将透明地报告为成功,并且您甚至不会收到请求尝试两次的通知。如果不是,则报告第二个请求失败(但不是第一个)
这种行为的问题是,我通过多部分请求将大文件上载到服务器,因此当请求尝试两次时,整个
POST
正文将发送两次,这是一种可怕的开销

我已尝试将
授权
标题手动添加到会话配置的
httpAdditionalHeaders
中,但仅当在创建会话之前设置了属性时,此操作才有效。之后试图修改会话.configuration.httpAdditionalHeaders
的操作无效。文档中还明确指出,
授权
标题不应手动设置


所以我的问题是:如果我需要在获得凭据之前启动会话,并且如果我想确保在第一次发出请求时始终使用正确的
授权
标题,我该怎么办


下面是我在测试中使用的代码示例。你可以用它重现我上面描述的所有行为

请注意,为了能够看到双重请求,您需要使用自己的http服务器并记录请求,或者通过记录所有请求的代理进行连接(我已经为此使用了代理)

注1:当向同一端点发出一行多个请求时,我上面描述的行为只涉及第一个请求。第一次使用正确的
授权
头尝试后续请求。但是,如果您等待一段时间(大约1分钟),会话将返回默认行为(第一个请求尝试了两次)

注2:这没有直接关系,但对会话配置的
urlCredentialStorage
使用自定义
NSURLCredentialStorage
似乎不起作用。仅使用默认值(根据文档,它是共享的
NSURLCredentialStorage
)有效


注3:我曾尝试使用Alamofire,但由于它基于
NSURLSession
,因此其行为方式与之完全相同。

如果可能,服务器应在客户端完成发送正文之前很久发出错误响应。然而,在许多高级服务器端语言中,这是很困难的,而且即使您这样做,也不能保证上传会停止

真正的问题是,您正在使用单个POST请求执行大型上载。这使得身份验证成为问题,并且如果在上传过程中连接中断,也会阻止任何有用的上传继续。分块上传基本上解决了所有问题:

  • 对于您的第一个请求,只发送适合的数量,而不添加额外的以太网数据包,即计算您的典型头大小,mod乘以1500字节,添加几十个字节以便更好地测量,从1500中减去,并为您的第一个数据块硬编码该大小。最多,你浪费了几包

  • 对于后续块,请将大小放大

  • 当请求失败时,询问服务器它得到了多少,然后从停止上载的位置重试

  • 发出请求,在完成上载后通知服务器

  • 使用cron作业或其他方法定期清除服务器端的部分上载


也就是说,如果您不能控制服务器端,通常的解决方法是在POST请求之前发送一个经过身份验证的GET请求。这将最大限度地减少浪费的数据包,同时只要网络可靠,大部分数据包仍能正常工作。

为什么不让第一个请求除了获得适当的授权标头外,始终不做任何工作?下一个请求(和向前)做所有的重提。尽量不要覆盖<代码> DeDeAccEvestPrime @ Gelnay.这是一个我甚至不想考虑的丑陋的解决方案。仍然毫无理由地消耗带宽,正如我所说的,这不仅仅是会话的第一个请求,而是每次停留约1分钟而不发送任何内容时的第一个请求,因此这也非常不切实际。@RunLoop尝试过这个方法。还尝试使用完成处理程序中的每个可能答案。不起作用。请尝试使用临时选项设置NSURLSession,并仅使用附加标题选项设置授权。谢谢您的回答。我已经在做分块上传了,但是分块本身仍然很大。现在,我找到了一种方法,可以在每个请求中注入适当的头,这样它就可以正常工作了。我希望在
URLSession
级别找到解决方案。我感到非常惊讶的是,我不能预先配置身份验证,而是必须
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)
        }
    }
}