3

我正在尝试从 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。
4

2 回答 2

4

有两种不同的委托协议:用于 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)
        }
    }
}
于 2018-11-21T12:59:36.830 回答
2

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
        }
    }

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

于 2018-08-22T10:03:43.630 回答