Получение всех файлов cookie от WKWebView

Получая куки от UIWebView кажется простым с помощью NSHTTPCookieStorage.sharedHTTPCookieStorage(), похоже на то WKWebView хранит печенье где-то еще.

Я провел некоторое исследование, и мне удалось получить несколько файлов cookie, взяв их у NSHTTPURLResponse объект. это, однако, не содержит все куки, используемые WKWebView:

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

Странно, но есть и класс WKWebsiteDataStore в IOS 9, который отвечает за управление куки в WKWebViewоднако класс не содержит открытый метод для получения данных 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?

14 ответов

Решение

В заключение, httpCookieStore за WKWebsiteDataStore приземлился в iOS 11.

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

Файлы cookie, используемые (созданные) WKWebView на самом деле правильно хранятся в NSHTTPCookieStorage.sharedHTTPCookieStorage(),

Проблема в том, что WKWebView не возвращает обратно куки немедленно. Я думаю, что это происходит по собственному графику. Например, когда WKWebView закрыто или, может быть, периодически.

Так что в конечном итоге они оказываются там, но когда это непредсказуемо.

Возможно, вы сможете принудительно синхронизировать общий ресурс NSHTTPCookieStorage закрыв WKWebView, Пожалуйста, дайте нам знать, если это работает.

Обновление: я только что вспомнил, что в Firefox для iOS мы форсируем WKWebView очистить свои внутренние данные, включая куки, заменив WKProcessPool с новым. Официального API нет, но я уверен, что это самый надежный обходной путь на данный момент.

подробности

Xcode 9.2, Swift 4

Решение

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

Полный образец

  1. Не забудьте добавить код решения здесь
  2. ViewController имеет встроенный контроллер представления
import UIKit
import WebKit

class ViewController: UIViewController {

    var urlString = "http://google.com"
    var webView: WKWebView!
    fileprivate var webViewIsInited = false
    override func viewDidLoad() {
        super.viewDidLoad()
    }

    override func viewWillLayoutSubviews() {
        if !webViewIsInited {
            webViewIsInited = true
            if webView == nil {
                webView = WKWebView(frame: UIScreen.main.bounds, configuration: WKWebViewConfiguration())
            }

            view.addSubview(webView)
            webView.navigationDelegate = self
            webView.uiDelegate = self
            webView.loadUrl(string: urlString)
        }
    }
}

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? {
        if navigationAction.targetFrame == nil {
            let vc = ViewController()
            vc.urlString = navigationAction.request.url?.absoluteString ?? "http://google.com"
            vc.view.frame = UIScreen.main.bounds
            vc.webView = WKWebView(frame: UIScreen.main.bounds, configuration: configuration)
            navigationController?.pushViewController(vc, animated: false)
            return vc.webView
        }
        return nil
    }
}

extension WKWebView {

    func loadUrl(string: String) {
        if let url = URL(string: string) {
            if self.url?.host == url.host {
                self.reload()
            } else {
                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)
    }

Следуйте ответу Стефана Арента и Фенома.

Для iOS 11 без расширений:

func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
    self.webView.configuration.websiteDataStore.httpCookieStore.getAllCookies { cookies in
        for cookie in cookies {
            //...
        }
    }
}
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];
      }];
}

Я использовал WKHTTPCookieStore в Objective-C, это сработало для меня, чтобы получить как постоянные, так и сессионные куки, но это работает только в 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);
        }
 }];

Заставить WKWebView сбрасывать свои внутренние данные, заменив его WKProcessPool, как описано в ответе Стефана, работал для меня в iOS 10 и 11, но только для постоянных файлов cookie; похоже, что сессионные куки удаляются, как описал J. Thoo

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

Как отметил Стефан, куки хранятся в NSHTTPCookieStorage.sharedHTTPCookieStorage()

Однако из моих экспериментов я обнаружил, что куки-файлы сеанса, установленные сервером, не видны NSHTTPCookieStorage.sharedHTTPCookieStorage(),

Пока каждый WKWebView поделиться тем же экземпляром WKProcessPoolэти файлы cookie сеанса будут передаваться на сервер для каждого запроса. Если вы измените пул процессов для WKWebViewвы по существу удаляете сеансовые куки для всех будущих запросов.

Не тратьте свое время на извлечение куки из iOS 11 below deviceшансов на успех очень мало. Из-за некоторых причин безопасности извлечение файлов 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)
    }
}

В NSHTTPCookie.cookiesWithResponseHeaderFields(headers, forURL: url)Что произойдет, если URL-адрес, на котором установлены файлы cookie, не является URL-адресом ответа навигации (URL, который вызывает навигацию)? Я замечаю, что URL обратного вызова, в котором установлены файлы cookie, никогда не вызывается в definePolicyFor navigationResponse.

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 обратного вызова, так как сам обратный вызов не вызвал навигацию по странице.

печенье (withResponseHeaderFields: для:)

Решено для iOS 11 или выше, единственное, что вам нужно сделать, это написать метод assíncronos, чтобы получить все файлы cookie следующим образом:

      webView.configuration.processPool = [[WKProcessPool alloc] init];
[[WKWebsiteDataStore defaultDataStore].httpCookieStore getAllCookies:^(NSArray<NSHTTPCookie *> * cookies) {
    //DO YOUR STUFF WITH THE 'cookies' VARIABLE
}

На практике я обнаружил, что в методе "managePolicyForNavigationResponse" вы можете использовать следующий способ получения файлов cookie, но, к сожалению, это не полный / полный список для сеанса.

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

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

Этот пост содержит полезную информацию об обработке файлов cookie с помощью WKWebView. В соответствии с этим вы должны иметь возможность устанавливать и получать файлы cookie, используя стандартные NSURLCache и NSHTTPCookie. Он также ссылается на использование WKProccessPool в соответствии с комментарием Стефана.

Другие вопросы по тегам