从 WKWebView 获取所有 cookie

Getting all cookies from WKWebView

虽然使用 NSHTTPCookieStorage.sharedHTTPCookieStorage()UIWebView 获取 cookie 似乎很简单,但似乎 WKWebView 将 cookie 存储在其他地方。

我做了一些研究,并且能够通过从 NSHTTPURLResponse 对象中抓取它来获得一些 cookie。但是,这不包含 WKWebView:

使用的所有 cookie
func webView(webView: WKWebView, decidePolicyForNavigationResponse navigationResponse: WKNavigationResponse, decisionHandler: (WKNavigationResponsePolicy) -> Void) {

  if let httpResponse = navigationResponse.response as? NSHTTPURLResponse {
    if let headers = httpResponse.allHeaderFields as? [String: String], url = httpResponse.URL {
      let cookies = NSHTTPCookie.cookiesWithResponseHeaderFields(headers, forURL: url)

      for cookie in cookies {
        logDebug(cookie.description)

        logDebug("found cookie " + cookie.name + " " + cookie.value)
      }
    }
  }
}

奇怪的是,ios9中还有一个classWKWebsiteDataStore负责管理WKWebView中的cookie,但是class中不包含public 检索 cookie 数据的方法:

let storage = WKWebsiteDataStore.defaultDataStore()

storage.fetchDataRecordsOfTypes([WKWebsiteDataTypeCookies], completionHandler: { (records) -> Void in
  for record in records {
    logDebug("cookie record is " + record.debugDescription)

    for dataType in record.dataTypes {
      logDebug("data type is " + dataType.debugDescription)

      // get cookie data??
    }
  }
})

是否有获取 cookie 数据的解决方法?

WKWebView 使用(创建)的 Cookie 实际上正确存储在 NSHTTPCookieStorage.sharedHTTPCookieStorage()

问题是 WKWebView 没有立即写回 cookie。我认为它按自己的时间表进行。例如,当 WKWebView 关闭或可能定期关闭时。

所以最终他们确实会在那里,但是什么时候是不可预测的。

您可以通过关闭 WKWebView 将 'sync' 强制到共享 NSHTTPCookieStorage。如果这有效,请告诉我们。

更新:我只记得在Firefox for iOS中我们强制WKWebView通过替换它的[=17来刷新它的内部数据,包括cookies =] 用一个新的。没有官方的 API,但我很确定这是目前最可靠的解决方法。

This post 包含有关使用 WKWebView 处理 cookie 的有用信息。据此,您应该能够使用标准的 NSURLCache 和 NSHTTPCookie 设置和检索 cookie。他还提到根据 Stephan 的评论使用 WKProccessPool。

正如 Stefan 提到的,cookie 存储在 NSHTTPCookieStorage.sharedHTTPCookieStorage()

但是,根据我的实验,我发现服务器设置的会话 cookie 对 NSHTTPCookieStorage.sharedHTTPCookieStorage() 不可见。

只要每个 WKWebView 共享 WKProcessPool 的同一个实例,这些会话 cookie 将针对每个请求传回服务器。如果您更改 WKWebView 的进程池,您实际上是在删除所有未来请求的会话 cookie。

在实践中,我发现在"decidePolicyForNavigationResponse"的方法中,你可以使用下面的方法来获取cookie,但遗憾的是它不是一个会话的complete/whole列表。

let response = navigationResponse.response as! NSHTTPURLResponse
        let headFields = response.allHeaderFields as! [String:String]

        let cookies = NSHTTPCookie.cookiesWithResponseHeaderFields(headFields, forURL: response.URL!)

NSHTTPCookie.cookiesWithResponseHeaderFields(headers, forURL: url) 中,如果设置 cookie 的 url 不是导航响应 url(url 导致导航)会怎样?我注意到在 decidePolicyFor navigationResponse 中从未调用设置 cookie 的回调 url。

func webView(_ webView: WKWebView, decidePolicyFor navigationResponse: WKNavigationResponse, decisionHandler: @escaping (WKNavigationResponsePolicy) -> Void) {
    let response = navigationResponse.response as! HTTPURLResponse
    let cookies  = HTTPCookie.cookies(withResponseHeaderFields: response.allHeaderFields as! [String : String], for: response.url!) 
}

以上委托永远不会为回调 url 执行,因为回调本身不会引起页面导航。

cookies(withResponseHeaderFields:for:)

最后,httpCookieStore for WKWebsiteDataStore 降落在 iOS 11.

https://developer.apple.com/documentation/webkit/wkwebsitedatastore?changes=latest_minor

详情

  • Xcode9.2,Swift4
  • Xcode 10.2 (10E125), Swift 5

解决方案

extension WKWebView {

    private var httpCookieStore: WKHTTPCookieStore  { return WKWebsiteDataStore.default().httpCookieStore }

    func getCookies(for domain: String? = nil, completion: @escaping ([String : Any])->())  {
        var cookieDict = [String : AnyObject]()
        httpCookieStore.getAllCookies { cookies in
            for cookie in cookies {
                if let domain = domain {
                    if cookie.domain.contains(domain) {
                        cookieDict[cookie.name] = cookie.properties as AnyObject?
                    }
                } else {
                    cookieDict[cookie.name] = cookie.properties as AnyObject?
                }
            }
            completion(cookieDict)
        }
    }
}

用法

// get cookies for domain
webView.getCookies(for: url.host) { data in
      print("=========================================")
      print("\(url.absoluteString)")
      print(data)
}

// get all cookies
webView.getCookies() { data in
      print("=========================================")
      print("\(url.absoluteString)")
      print(data)
}

完整样本

Info.plist

添加您的 Info.plist 传输安全设置

 <key>NSAppTransportSecurity</key>
 <dict>
    <key>NSAllowsArbitraryLoads</key>
    <true/>
 </dict>

代码

  1. Do not forget to add the solution code here
  2. ViewController has embed view controller
import UIKit
import WebKit

class ViewController: UIViewController {

    private lazy var url = URL(string: "https://google.com")!
    private weak var webView: WKWebView?

    func initWebView(configuration: WKWebViewConfiguration) {
        if webView != nil { return }
        let webView = WKWebView(frame: UIScreen.main.bounds, configuration: configuration)
        webView.navigationDelegate = self
        webView.uiDelegate = self
        view.addSubview(webView)
        self.webView = webView
    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        if webView == nil { initWebView(configuration: WKWebViewConfiguration()) }
        webView?.load(url: url)
    }
}

extension ViewController: WKNavigationDelegate {

    func webView(_ webView: WKWebView, decidePolicyFor navigationResponse: WKNavigationResponse, decisionHandler: @escaping (WKNavigationResponsePolicy) -> Void) {
        decisionHandler(.allow)
    }

    func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
        if let url = webView.url {
            webView.getCookies(for: url.host) { data in
                print("=========================================")
                print("\(url.absoluteString)")
                print(data)
            }
        }
    }
}

extension ViewController: WKUIDelegate {

    func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView? {
        // push new screen to the navigation controller when need to open url in another "tab"
        if let url = navigationAction.request.url, navigationAction.targetFrame == nil {
            let viewController = ViewController()
            viewController.initWebView(configuration: configuration)
            viewController.url = url
            DispatchQueue.main.async { [weak self] in
                self?.navigationController?.pushViewController(viewController, animated: true)
            }
            return viewController.webView
        }
        return nil
    }
}

extension WKWebView {

    func load(urlString: String) {
        if let url = URL(string: urlString) { load(url: url) }
    }

    func load(url: URL) { load(URLRequest(url: url)) }
}

我知道这是一个非常古老的问题,我们有一个解决方案,但只适用于 iOS 11 及更高版本。对于 iOS 10 及以下的人(比如我),您可以考虑这种方法。它非常适合我:

  • 强制重置进程池:
extension WKWebView {
    func refreshCookies() {
        self.configuration.processPool = WKProcessPool()
        // TO DO: Save your cookies,...
    }
}

--> 这只适用于真实设备。

  • 对于模拟器,你应该添加:
func webView(_ webView: WKWebView, decidePolicyFor navigationResponse: WKNavigationResponse, decisionHandler: @escaping (WKNavigationResponsePolicy) -> Void) {
    if let response = navigationResponse.response as? HTTPURLResponse,
       let allHttpHeaders = response.allHeaderFields as? [String: String],
       let responseUrl = response.url {
        let cookies = HTTPCookie.cookies(withResponseHeaderFields: allHttpHeaders, for: responseUrl)

        for cookie in cookies {
            HTTPCookieStorage.shared.setCookie(cookie)
        }
    }

    decisionHandler(.allow)
}

关注 Stefan Arentz 和 Phenom 的回答。

我在 Objective-C 中使用了 WKHTTPCookieStore,这对我获得持久性和会话 cookie 都有效,但它仅在 iOS 11+

中有效

https://developer.apple.com/documentation/webkit/wkhttpcookiestore?changes=latest_minor&language=objc

 if (@available(iOS 11.0, *)) {
     WKHTTPCookieStore *cookieStore = _webView.configuration.websiteDataStore.httpCookieStore;
     [cookieStore getAllCookies:^(NSArray* cookies) {
        NSHTTPCookie *cookie;
        for(cookie in cookies){
            NSLog(@"cookie: %@", cookie);
        }
 }];

按照 Stefan 的回答所述,通过替换其 WKProcessPool 来强制 WKWebView 刷新其内部数据在 iOS 10 和 11 中对我有用,但仅适用于持久性 cookie;正如 J. Thoo 所描述的那样,会话 cookie 似乎被删除了

if (@available(iOS 11.0, *)) {
  [webView.configuration.websiteDataStore.httpCookieStore
      getAllCookies:^(NSArray<NSHTTPCookie *> *_Nonnull cookies) {
        NSURLRequest *request =
            [[NSURLRequest alloc] initWithURL:self.URL]; //your URL
        NSURLSession *session = [NSURLSession sharedSession];
        NSURLSessionDataTask *task = [session
            dataTaskWithRequest:request
              completionHandler:^(NSData *responseData, NSURLResponse *response,
                                  NSError *error) {
                //Do Something
              }];
        [task resume];
        [session.configuration.HTTPCookieStorage storeCookies:cookies forTask:task];
      }];
}

不要浪费时间从iOS 11 below device中提取cookie,成功的机会很小。由于某些安全原因,Cookie 提取可能会被阻止。

参考这些日志:

2019-02-07 00:05:45.548880+0530 MyApp[2278:280725] [BoringSSL] nw_protocol_boringssl_get_output_frames(1301) [C8.1:2][0x10fd776f0] get output frames failed, state 8196

2019-02-07 00:05:45.550915+0530 MyApp[2278:280725] TIC Read Status [8:0x0]: 1:57

试试这个为以下 iOS 11 台设备构建的代码:

func webView(_ webView: WKWebView, decidePolicyFor navigationResponse: WKNavigationResponse, decisionHandler: @escaping (WKNavigationResponsePolicy) -> Void) {
        let cookieValue = HTTPCookieStorage.shared.cookies(for: navigationResponse.response.url!)
        print(cookieValue!)
        let response = navigationResponse.response as! HTTPURLResponse
        let headFields = response.allHeaderFields as! [String:String]

        let cookies = HTTPCookie.cookies(withResponseHeaderFields: headFields, for: response.url!)
        for cookie in cookies {
            print("name: \(cookie.name) value: \(cookie.value)")
        }
        decisionHandler(.allow)
    }

以上代码将为您提供空的 cookie 数组,因为出于某些安全原因,cookie 提取被阻止。

我建议您尝试以下适用于 iOS 11 及更高版本的内容:

WKWebsiteDataStore.default().httpCookieStore.getAllCookies { (cookies) in
    for cookie in cookies {
        print(cookie)
    }
}

Swift 5

func webView(_ webView: WKWebView, decidePolicyFor navigationResponse: WKNavigationResponse, decisionHandler: @escaping (WKNavigationResponsePolicy) -> Void) {
    webView.configuration.websiteDataStore.httpCookieStore.getAllCookies { cookies in
        debugPrint(cookies.debugDescription)
    }

    decisionHandler(.allow)
}

对于iOS 11,没有任何扩展名:

func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
    self.webView.configuration.websiteDataStore.httpCookieStore.getAllCookies { cookies in
        for cookie in cookies {
            //...
        }
    }
}