it-roy-ru.com

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

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

Я провел некоторое исследование, и мне удалось получить некоторые файлы cookie, взяв их с объекта NSHTTPURLResponse. это, однако, не содержит всех файлов cookie, используемых 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)
      }
    }
  }
}

Как ни странно, в ios 9 также есть класс WKWebsiteDataStore, который отвечает за управление файлами cookie в 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?

46
aporat

Наконец, httpCookieStore для WKWebsiteDataStore появился в iOS 11.

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

13
Tualatrix Chou

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

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

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

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

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

55
Stefan Arentz

Подробности

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

 enter image description here

14
Vasily Bodnarchuk

Я знаю, что это очень старый вопрос, и у нас есть решение, но мы работаем только на 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)
    }

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

8
November Rain

Я использовал 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

2
Jorge Duque

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

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

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

1
J.Thoo
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];
      }];
}
0
Vivek

Не тратьте свое время на извлечение файлов cookie из 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)
    }
}
0
HSAM

В 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: для :)

0
Qin Zhengquan

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

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

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