WKWebView持久存储Cookie

hap*_*384 32 cookies ios wkwebview

我在我的原生iPhone应用程序中,在允许登录/注册的网站上使用WKWebView,并将会话信息存储在cookie中.我试图找出如何持久存储cookie信息,因此当应用程序重新启动时,用户仍然可以使用其Web会话.

我在应用程序中有2个WKWebViews,它们共享一个WKProcessPool.我从一个共享进程池开始:

WKWebView

然后为每个WKWebView:

WKWebViewConfiguration *theConfiguration = [[WKWebViewConfiguration alloc] init]; 
theConfiguration.processPool = processPool; 
self.webView = [[WKWebView alloc] initWithFrame:frame configuration:theConfiguration];
Run Code Online (Sandbox Code Playgroud)

当我使用第一个WKWebView登录,然后一段时间后将操作传递给第二个WKWebView时,会话保留,因此cookie已成功共享.但是,当我重新启动应用程序时,会创建一个新的进程池并销毁会话信息.有没有办法让会话信息通过应用程序重启持续存在?

Ger*_*ero 18

其实,这是因为有一个)一些艰难的一个缺陷是仍然没有被苹果(我认为)解决和b)取决于你想要的,我想饼干.

我现在无法测试,但我可以给你一些指示:

  1. 从中获取cookie NSHTTPCookieStorage.sharedHTTPCookieStorage().这个看起来很麻烦,显然饼干没有立即保存NSHTTPCookieStorage以便找到它们.人们建议通过重置进程池来触发保存,但我不知道这是否可靠.不过,您可能想亲自尝试一下.
  2. 进程池实际上不是保存cookie的原因(尽管它定义了它们是否按照您正确的说明进行共享).文档说的是WKWebsiteDataStore,所以我会查看.至少从那里获取cookie fetchDataRecordsOfTypes:completionHandler:可能是可能的(不知道如何设置它们,但我认为你不能仅仅因为与进程池相同的原因而将存储保存在用户默认值中).
  3. 你应该设法获得你需要的cookie(或者更确切地说是它们的值),但是不能恢复它们,因为我猜是这样的,请看这里(基本上它显示了如何简单地用它们准备httprequest,相关部分:[request addValue:@"TeskCookieKey1=TeskCookieValue1;TeskCookieKey2=TeskCookieValue2;" forHTTPHeaderField:@"Cookie"]).
  4. 如果一切都失败了,请检查一下.我知道提供只是链接只有答案是不好的,但我不能复制所有这些,只是想为了完整性添加它.

最后一件事:我说你的成功也可能取决于cookie的类型.这是因为这个答案表明服务器设置的cookie无法访问NSHTTPCookieStorage.我不知道这是否与你相关(但我想是的,因为你可能正在寻找一个会话,即服务器设置的cookie,对吗?)我不知道这是否意味着其他方法失败了同样.

如果所有其他方法都失败了,您可以考虑在某处保存用户凭据(例如,keychain),并在下一个应用程序上重新使用它们以自动进行身份验证.这可能无法恢复所有会话数据,但考虑到用户退出可能实际需要的应用程序?也或许某些值可以被捕获并使用一个注入脚本保存以备以后使用,就像提到这里(显然不适合在启动设置它们,但也许在某个时刻检索它们.你需要知道该网站是如何工作的,当然以后,) .

我希望至少可以指出你解决问题的一些新方向.它并不像它应该的那样微不足道,它似乎(然后,会话cookie是一种安全相关的东西,所以也许隐藏它们远离应用程序是Apple的有意识的设计选择...).


Har*_*hak 10

经过几天的研究和实验,我找到了一个在 WKWebView 中管理会话的解决方案,这是一个解决方法,因为我没有找到任何其他方法来实现这一点,以下是步骤:

首先,您需要创建方法来设置和获取用户默认值中的数据,当我说数据时,它意味着 NSData,这里是方法。

+(void)saveDataInNSDefault:(id)object key:(NSString *)key{
    NSData *encodedObject = [NSKeyedArchiver archivedDataWithRootObject:object];
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    [defaults setObject:encodedObject forKey:key];
    [defaults synchronize];
}

+ (id)getDataFromNSDefaultWithKey:(NSString *)key{
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    NSData *encodedObject = [defaults objectForKey:key];
    id object = [NSKeyedUnarchiver unarchiveObjectWithData:encodedObject];
    return object;
}
Run Code Online (Sandbox Code Playgroud)

为了在 webview 上维护会话,我制作了 webview 和 WKProcessPool 单例。

- (WKWebView *)sharedWebView {
    static WKWebView *singleton;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        WKWebViewConfiguration *webViewConfig = [[WKWebViewConfiguration alloc] init];
        WKUserContentController *controller = [[WKUserContentController alloc] init];

        [controller addScriptMessageHandler:self name:@"callNativeAction"];
        [controller addScriptMessageHandler:self name:@"callNativeActionWithArgs"];
        webViewConfig.userContentController = controller;
        webViewConfig.processPool = [self sharedWebViewPool];

        singleton = [[WKWebView alloc] initWithFrame:self.vwContentView.frame configuration:webViewConfig];

    });
    return singleton;
}

- (WKProcessPool *)sharedWebViewPool {
    static WKProcessPool *pool;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{

        pool = [Helper getDataFromNSDefaultWithKey:@"pool"];

        if (!pool) {
            pool = [[WKProcessPool alloc] init];
        }

    });
    return pool;
}
Run Code Online (Sandbox Code Playgroud)

在 ViewDidLoad 中,我检查它是否不是登录页面,并将 cookie 从用户默认值加载到 HttpCookieStore 中,以便通过身份验证或使用这些 cookie 来维护会话。

if (!isLoginPage) {
            [request setValue:accessToken forHTTPHeaderField:@"Authorization"];

            NSMutableSet *setOfCookies = [Helper getDataFromNSDefaultWithKey:@"cookies"];
            for (NSHTTPCookie *cookie in setOfCookies) {
                if (@available(iOS 11.0, *)) {

                    [webView.configuration.websiteDataStore.httpCookieStore setCookie:cookie completionHandler:^{}];
                } else {
                    // Fallback on earlier versions
                    [[NSHTTPCookieStorage sharedHTTPCookieStorage] setCookie:cookie];
                }
            }
        }
Run Code Online (Sandbox Code Playgroud)

并且,加载请求。

现在,我们将使用 cookie 维护 webview 会话,因此在您的登录页面 webview 上,将 httpCookieStore 中的 cookie 保存到 viewDidDisappear 方法中的用户默认值中。

- (void)viewDidDisappear:(BOOL)animated {

    if (isLoginPage) { //checking if it’s login page.
        NSMutableSet *setOfCookies = [Helper getDataFromNSDefaultWithKey:@"cookies"]?[Helper getDataFromNSDefaultWithKey:@"cookies"]:[NSMutableArray array];
        //Delete cookies if >50
        if (setOfCookies.count>50) {
            [setOfCookies removeAllObjects];
        }
        if (@available(iOS 11.0, *)) {
            [webView.configuration.websiteDataStore.httpCookieStore getAllCookies:^(NSArray<NSHTTPCookie *> * _Nonnull arrCookies) {

                for (NSHTTPCookie *cookie in arrCookies) {
                    NSLog(@"Cookie: \n%@ \n\n", cookie);
                    [setOfCookies addObject:cookie];
                }
                [Helper saveDataInNSDefault:setOfCookies key:@"cookies"];
            }];
        } else {
            // Fallback on earlier versions
            NSArray *cookieStore = NSHTTPCookieStorage.sharedHTTPCookieStorage.cookies;
            for (NSHTTPCookie *cookie in cookieStore) {
                NSLog(@"Cookie: \n%@ \n\n", cookie);
                [setOfCookies addObject:cookie];
            }
            [Helper saveDataInNSDefault:setOfCookies key:@"cookies"];
        }
    }

    [Helper saveDataInNSDefault:[self sharedWebViewPool] key:@"pool"];
}
Run Code Online (Sandbox Code Playgroud)

注意:以上方法仅针对 iOS 11 进行了测试,虽然我也为较低版本编写了后备但没有测试这些。

希望这能解决你的问题!!!:)

  • 大大的“谢谢!!!” 发布此解决方案!如上所述 - 这是我在有关该主题的大量记录不完善/过时的文章中找到的唯一 ios14 解决方案 (2认同)

Mou*_*our 10

最后,我找到了在WKWebView中管理会话的解决方案,在swift 4下工作,但该解决方案可以携带到swift 3或object-C:

class ViewController: UIViewController {

let url = URL(string: "https://insofttransfer.com")!


@IBOutlet weak var webview: WKWebView!

override func viewDidLoad() {

    super.viewDidLoad()
    webview.load(URLRequest(url: self.url))
    webview.uiDelegate = self
    webview.navigationDelegate = self
}}
Run Code Online (Sandbox Code Playgroud)

为 WKWebview 创建扩展...

extension WKWebView {

enum PrefKey {
    static let cookie = "cookies"
}

func writeDiskCookies(for domain: String, completion: @escaping () -> ()) {
    fetchInMemoryCookies(for: domain) { data in
        print("write data", data)
        UserDefaults.standard.setValue(data, forKey: PrefKey.cookie + domain)
        completion();
    }
}


 func loadDiskCookies(for domain: String, completion: @escaping () -> ()) {
    if let diskCookie = UserDefaults.standard.dictionary(forKey: (PrefKey.cookie + domain)){
        fetchInMemoryCookies(for: domain) { freshCookie in

            let mergedCookie = diskCookie.merging(freshCookie) { (_, new) in new }

            for (cookieName, cookieConfig) in mergedCookie {
                let cookie = cookieConfig as! Dictionary<String, Any>

                var expire : Any? = nil

                if let expireTime = cookie["Expires"] as? Double{
                    expire = Date(timeIntervalSinceNow: expireTime)
                }

                let newCookie = HTTPCookie(properties: [
                    .domain: cookie["Domain"] as Any,
                    .path: cookie["Path"] as Any,
                    .name: cookie["Name"] as Any,
                    .value: cookie["Value"] as Any,
                    .secure: cookie["Secure"] as Any,
                    .expires: expire as Any
                ])

                self.configuration.websiteDataStore.httpCookieStore.setCookie(newCookie!)
            }

            completion()
        }

    }
    else{
        completion()
    }
}

func fetchInMemoryCookies(for domain: String, completion: @escaping ([String: Any]) -> ()) {
    var cookieDict = [String: AnyObject]()
    WKWebsiteDataStore.default().httpCookieStore.getAllCookies { (cookies) in
        for cookie in cookies {
            if cookie.domain.contains(domain) {
                cookieDict[cookie.name] = cookie.properties as AnyObject?
            }
        }
        completion(cookieDict)
    }
}}
Run Code Online (Sandbox Code Playgroud)

然后像这样为我们的视图控制器创建一个扩展

extension ViewController: WKUIDelegate, WKNavigationDelegate {
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
   //load cookie of current domain
    webView.loadDiskCookies(for: url.host!){
        decisionHandler(.allow)
    }
}

public func webView(_ webView: WKWebView, decidePolicyFor navigationResponse: WKNavigationResponse, decisionHandler: @escaping (WKNavigationResponsePolicy) -> Void) {
   //write cookie for current domain
    webView.writeDiskCookies(for: url.host!){
        decisionHandler(.allow)
    }
}
}
Run Code Online (Sandbox Code Playgroud)

url当前网址在哪里:

    let url = URL(string: "https://insofttransfer.com")!
Run Code Online (Sandbox Code Playgroud)


jar*_*ora 9

我回答这个问题有点晚了,但我想为现有答案添加一些见解。这里提到的答案为 WKWebView 上的 Cookie 持久性提供了有价值的信息。但是,有一些注意事项。

  1. WKWebView不适NSHTTPCookieStorage用于 ,因此对于 iOS 8、9、10,您将不得不使用 UIWebView。
  2. 您不一定需要将 保持WKWebView为单例,但您确实需要WKProcessPool每次都使用相同的实例 来再次获取所需的 cookie。
  3. 最好先使用setCookie 方法设置 cookie ,然后实例化WKWebView.

我还想重点介绍 Swift 中的 iOS 11+ 解决方案。

let urlString = "http://127.0.0.1:8080"
var webView: WKWebView!
let group = DispatchGroup()
    
override func viewDidLoad() {
    super.viewDidLoad()
    self.setupWebView { [weak self] in
        self?.loadURL()
    }
}

override func viewDidDisappear(_ animated: Bool) {
    super.viewDidDisappear(animated)
    if #available(iOS 11.0, *) {
        self.webView.configuration.websiteDataStore.httpCookieStore.getAllCookies { cookies in
            self.setData(cookies, key: "cookies")
        }
    } else {
        // Fallback on earlier versions
    }
}

private func loadURL() {
    let urlRequest = URLRequest(url: URL(string: urlString)!)
    self.webView.load(urlRequest)
}
    
private func setupWebView(_ completion: @escaping () -> Void) {
    
    func setup(config: WKWebViewConfiguration) {
        self.webView = WKWebView(frame: CGRect.zero, configuration: config)
        self.webView.navigationDelegate = self
        self.webView.uiDelegate = self
        self.webView.translatesAutoresizingMaskIntoConstraints = false
        self.view.addSubview(self.webView)
        
        NSLayoutConstraint.activate([
            self.webView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor),
            self.webView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor),
            self.webView.topAnchor.constraint(equalTo: self.view.topAnchor),
            self.webView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor)])
    }
    
    self.configurationForWebView { config in
        setup(config: config)
        completion()
    }
    
}

private func configurationForWebView(_ completion: @escaping (WKWebViewConfiguration) -> Void) {
            
    let configuration = WKWebViewConfiguration()
    
    //Need to reuse the same process pool to achieve cookie persistence
    let processPool: WKProcessPool

    if let pool: WKProcessPool = self.getData(key: "pool")  {
        processPool = pool
    }
    else {
        processPool = WKProcessPool()
        self.setData(processPool, key: "pool")
    }

    configuration.processPool = processPool
    
    if let cookies: [HTTPCookie] = self.getData(key: "cookies") {
        
        for cookie in cookies {
            
            if #available(iOS 11.0, *) {
                group.enter()
                configuration.websiteDataStore.httpCookieStore.setCookie(cookie) {
                    print("Set cookie = \(cookie) with name = \(cookie.name)")
                    self.group.leave()
                }
            } else {
                // Fallback on earlier versions
            }
        }
        
    }
    
    group.notify(queue: DispatchQueue.main) {
        completion(configuration)
    }
}
Run Code Online (Sandbox Code Playgroud)

辅助方法:

func setData(_ value: Any, key: String) {
    let ud = UserDefaults.standard
    let archivedPool = NSKeyedArchiver.archivedData(withRootObject: value)
    ud.set(archivedPool, forKey: key)
}

func getData<T>(key: String) -> T? {
    let ud = UserDefaults.standard
    if let val = ud.value(forKey: key) as? Data,
        let obj = NSKeyedUnarchiver.unarchiveObject(with: val) as? T {
        return obj
    }
    
    return nil
}
Run Code Online (Sandbox Code Playgroud)

编辑:我曾提到最好实例化WKWebView后期setCookie调用。我遇到了一些问题,其中setCookie第二次尝试打开WKWebView. 这似乎是 WebKit 中的一个错误。因此,我必须先实例化WKWebView,然后调用setCookie配置。确保仅在所有setCookie调用返回后才加载 URL 。


Tum*_*ata 8

经过广泛的搜索和手动调试,我得出了这些简单的结论(iOS11+)。

你需要考虑这两个类别:

  • 您正在使用WKWebsiteDataStore.nonPersistentDataStore

    那就WKProcessPool 无所谓了

    1. 使用提取 cookie websiteDataStore.httpCookieStore.getAllCookies()
    2. 将这些 cookie 保存到 UserDefaults(或者最好是钥匙串)中。
    3. ...
    4. 稍后当您从存储中重新创建这些 cookie 时,调用websiteDataStore.httpCookieStore.setCookie()每个 cookie 就可以了。
  • 您正在使用WKWebsiteDataStore.defaultDataStore

    然后WKProcessPool与配置相关的事情很重要。它必须与 cookie 一起保存。

    1. 将 webview 配置的 processPool 保存到 UserDefaults(或者最好是 Keychain)。
    2. 使用提取 cookie websiteDataStore.httpCookieStore.getAllCookies()
    3. 将这些 cookie 保存到 UserDefaults(或者最好是钥匙串)中。
    4. ...
    5. 稍后从存储重新创建进程池并将其分配给 web 视图的配置
    6. 从存储中重新创建 cookie 并调用websiteDataStore.httpCookieStore.setCookie()每个 cookie

注意:已经有许多详细的实现可用,所以我不会添加更多的实现细节来保持简单。