从WKWebView获取所有的cookie

81

UIWebView获取cookie似乎很简单,只需使用NSHTTPCookieStorage.sharedHTTPCookieStorage(),但是在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)
      }
    }
  }
}

奇怪的是,在iOS 9中还有一个类WKWebsiteDataStore,负责管理WKWebView中的cookie,但该类不包含公共方法来检索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 数据的方法?


值得注意的是,WebKit团队似乎正在致力于寻找一种适当的方式来访问WKWebView的cookie存储:https://bugs.webkit.org/show_bug.cgi?id=140191 - robotspacer
@aporat,你找到解决方案了吗?我已经在这个问题上工作了几个月,但是还没有找到任何解决方案:( - ZAFAR007
https://dev59.com/wVkS5IYBdhLWcg3we26Z#49651579 - Harish Pathak
@aporat,你没有提到获取cookie数据 :) - Muhammad Shauket
14个回答

76
WKWebView使用的cookie实际上是被正确存储在NSHTTPCookieStorage.sharedHTTPCookieStorage()中的。问题在于WKWebView不会立即回写cookie,我认为它会按照自己的计划进行操作。例如,当WKWebView关闭或定期执行时才会回写cookie。因此,它们最终会被存储,但是“何时”是不可预测的。你也许可以通过关闭WKWebView来强制同步到共享的NSHTTPCookieStorage中。请告诉我们这个方法是否奏效。更新:我刚刚想起,在Firefox for iOS中,我们通过替换其WKProcessPool强制刷新WKWebView的内部数据(包括cookie)。虽然没有官方API,但我相信这是目前最可靠的解决方法。

8
谢谢。我会查看它。你有一个执行WKProcessPool切换的函数的参考吗?我是不是只需要用一个新的池子替换掉原来的池子? - aporat
有人尝试过这个答案中描述的解决方法吗? - tapmonkey
“关闭 WKWebView” 的意思是什么?需要使用 removeFromSuperview 并将其设置为 nil 吗? - Lukas Würzburger
我需要关于WKWebView cookies的帮助。 - Siva
9
对于那些看到这里感到困惑的人,我的确执行了以下操作:self.webView.configuration.processPool = [[WKProcessPool alloc] init];它有效地清除了Cookie,以便在NSHTTPCookieStorage.sharedHTTPCookieStorage()中可用。但是,对我而言,它只在设备上工作,而不是模拟器上。 - Phenom
这个答案是否最新? - Jonny

31

iOS 10或以下怎么办? - Yogendra Patel

30

详细信息

  • Xcode 9.2,Swift 4
  • 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. 不要忘记在此处添加解决方案代码
  2. ViewController 包含嵌入的视图控制器
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)) }
}

输入图片说明


我能获取完整的项目吗? - Shahid Ghafoor
@ShahidGhafoor 当然,https://www.dropbox.com/s/kft7ue4zgn4p5hl/stackoverflow-33156567.zip?dl=0 - Vasily Bodnarchuk

16

我知道这是一个非常古老的问题,我们已经有了解决方案,但只适用于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的回答。


你忘记如何实际复制Cookies了吗? - Jonny
refreshCookies() 函数是在什么时候被调用的?我猜测在我们需要检索 cookies 之前?但是在我使用的网站上仍然无法正常工作,在运行 iOS 13.1.2 的实际设备上进行了测试。这是因为会话/持久化 cookies 的原因吗? - CyberMew
我回来了并投票支持,为了获取我添加的cookie,我使用了self.configuration.websiteDataStore.httpCookieStore.getAllCookies { (cookies) in /* your own code */}。目前只在设备上测试过(iOS 13)。 - Jonny
我总是得到一个空的“cookies”数组。请帮帮我。 - Yogendra Patel

10

对于没有任何扩展的iOS 11

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

我是一名Android开发人员,对IOS完全陌生,只需要解决这个问题!所以我的问题是,getAllCookies什么时候被调用?在webview.loadUrl之后还是之前?请给我一些指点,谢谢! - Junia Montana
1
当 Webview 中的网页完全加载时,需要调用它。 - Zeero0
1
非常感谢!我实际上解决了这个问题,一开始对我来说有点难。 - Junia Montana
或者在Objective-C中:webView.configuration.processPool = [[WKProcessPool alloc] init]; [[WKWebsiteDataStore defaultDataStore].httpCookieStore getAllCookies:^(NSArray * cookies) { //使用'cookies'变量进行操作 } - Breno Medeiros de Oliveira

5
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];
      }];
}

1
谢谢你的回答,对我很有帮助,但它只适用于iOS 11.0或更高版本。我想在iOS 10或更低版本中执行相同的操作。请帮帮我。这意味着我需要在iOS 10中获取所有的cookies。 - Yogendra Patel

4
我在 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);
        }
 }];

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

嗨,Jorge,你对iOS 13.0及以上版本有什么解决方案? - Erdogan

4

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

2

正如Stefan所提到的,cookies被存储在NSHTTPCookieStorage.sharedHTTPCookieStorage()中。

然而,根据我的实验结果,服务器设置的Session cookies对于NSHTTPCookieStorage.sharedHTTPCookieStorage()是不可见的。

只要每个WKWebView共享相同的WKProcessPool实例,这些Session cookies就会被传递回服务器以用于每个请求。如果您更改WKWebView的处理池,则会从所有未来的请求中删除会话cookies。


是的,可以说cookie存储在NSHTTPCookieStorage.sharedHTTPCookieStorage()中。然而,问题在于:如果用户已经在UIWebView中登录,那么它并不会自动登录到另一个WKWebView,反之亦然。因此,我的看法是:即使它们共享相同的cookie,它们背后的原理也是非常不同的。 - ikzjfr0

1
不要浪费时间从iOS 11以下设备中提取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)
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接