从 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 执行,因为回调本身不会引起页面导航。
最后,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>
代码
- Do not forget to add the solution code here
- 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+
中有效
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 {
//...
}
}
}
虽然使用 NSHTTPCookieStorage.sharedHTTPCookieStorage()
从 UIWebView
获取 cookie 似乎很简单,但似乎 WKWebView
将 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)
}
}
}
}
奇怪的是,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 执行,因为回调本身不会引起页面导航。
最后,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>
代码
- Do not forget to add the solution code here
- 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+
中有效 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 {
//...
}
}
}