【问题标题】:Trying to understand URLSession Authentication challenges试图了解 URLSession 身份验证挑战
【发布时间】:2019-01-28 12:51:35
【问题描述】:

我正在尝试从 URL 下载 PDF。

private func downloadSessionWithFileURL(_ url: URL){

        var request = URLRequest(url: url)

        request.addValue("gzip, deflate", forHTTPHeaderField: "Accept-Encoding")

        let sessionConfig = URLSessionConfiguration.default

        let session = URLSession(configuration: sessionConfig, delegate: self, delegateQueue: nil)
           session.downloadTask(with: request).resume()

    }

这会调用它的委托方法

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

        if challenge.previousFailureCount > 0 {
              completionHandler(Foundation.URLSession.AuthChallengeDisposition.cancelAuthenticationChallenge, nil)
        }
        if let serverTrust = challenge.protectionSpace.serverTrust {
          completionHandler(Foundation.URLSession.AuthChallengeDisposition.useCredential, URLCredential(trust: serverTrust))
 } else {
          print("unknown state. error: \(String(describing: challenge.error))")

       }
    }

URLAuthenticationChallenges protectionSpace 始终是 serverTrust。 当试图访问 PDF 的 URL 时,它会将用户重定向到登录屏幕。我原以为会有另一个电话

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

要求用户输入他们的凭据,但没有。因此下载任务尝试下载重定向 URL 的内容,这是一个登录屏幕。

我的问题是。

1. 触发用户名和密码的 URLAuthenticationChallenge 的原因。它是 HTML 中的特定标头值吗?

  1. 对于来自服务器的用户名密码请求,我应该期待哪个 URLAuthenticationChallenge protectionSpace。

【问题讨论】:

    标签: swift nsurlsessiondownloadtask urlauthenticationchallenges


    【解决方案1】:

    SSL 的一些基础知识:

    How SSL works? When client establishes the connection with server (called SSL handshake):
    Client connects to server and requests server identify itself.
    Server sends certificate to client (include public key)
    Client checks if that certificate is valid. If it is, client creates a symmetric key (session key), encrypts with public key, then sends back to server
    Server receives encrypted symmetric key, decrypts by its private key, then sends acknowledge packet to client
    Client receives ACK and starts the session
    

    1. 触发用户名和密码的 URLAuthenticationChallenge 的原因。它是 HTML 中的特定标头值吗?

    如果您有 https 连接,这些方法将被触发。这些是出于安全目的,以防止中间人攻击。例如,我可以设置 charles 代理服务器,在模拟器/设备上安装公共证书,并可以监控应用程序发送到实际服务器的所有请求,从而获取敏感信息(API 密钥、令牌、请求标头、请求我需要向攻击者隐藏的身体等)。

    我应该期待哪个 URLAuthenticationChallenge protectionSpace 来自服务器的用户名密码请求。

    您可以将服务器证书与应用中的本地证书进行比较:

    if let serverCertificate = SecTrustGetCertificateAtIndex(trust, 0) {
    
           let serverCertificateData = SecCertificateCopyData(serverCertificate) as Data
           let localCer = Bundle.main.path(forResource: "fileName", ofType: "cer")
    
            if let localCer = localCer {
    
                 if localCer.isEqual(to: serverCertificate) {                                             completionHandler(URLSession.AuthChallengeDisposition.useCredential, URLCredential(trust:serverTrust))
                     return
                 }
             }
      }
    

    或者您可以比较公钥:

     if let serverCertificate = SecTrustGetCertificateAtIndex(trust, 0), let serverCertificateKey = publicKey(for: serverCertificate) {
            if pinnedKeys().contains(serverCertificateKey) {
                completionHandler(.useCredential, URLCredential(trust: trust))
                return
            }
        }
    

    比较公钥是一种更好的方法,因为在比较证书时,您必须在应用程序中保留本地证书的副本,当证书过期时,必须更新应用程序中的证书,这需要更新应用商店。

    【讨论】:

      【解决方案2】:

      有两种不同的委托协议:用于 URLSession 本身及其任务。

      URLSessionDelegate 有:public func urlSession(_:didReceive:completionHandler:) URLSessionTaskDelegate 有:public func urlSession(_:task:didReceive:completionHandler:)

      URLSessionDelegate 用于服务器信任问题(例如,通过 Charles 或其他代理运行时允许 SSL 信任)。 URLSessionTaskDelegate 用于对单个任务进行身份验证。

      所以要获得您的身份验证挑战,请将其添加到您的课程中:

      extension MyClass: URLSessionTaskDelegate {
      
          public func urlSession(_ session: URLSession, task: URLSessionTask, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
      
              if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodDefault ||
                  challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodHTTPBasic {
      
                  let credential = URLCredential(user: self.basicAuthUserName,
                                                 password: self.basicAuthPassword,
                                                 persistence: .forSession)
      
                  completionHandler(.useCredential, credential)
              }
              else {
                  completionHandler(.performDefaultHandling, nil)
              }
          }
      }
      

      【讨论】:

      • 从哪里获得basicAuthUserNamebasicAuthPassword
      • @AwaisFayyaz 在这种情况下,他们将这两个定义为类中的实例变量。但是您可以轻松地从 View 中获取它们,并带有一些 TextField 输入。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-10-14
      • 2013-06-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-09-05
      相关资源
      最近更新 更多