ios cookies wkwebview

ios - WKWebView Almacenamiento persistente de cookies



(7)

Estoy usando un WKWebView en mi aplicación nativa de iPhone, en un sitio web que permite el inicio de sesión / registro y almacena la información de la sesión en cookies. Estoy tratando de averiguar cómo almacenar de forma persistente la información de las cookies, de modo que cuando la aplicación se reinicia, el usuario todavía tiene su sesión web disponible.

Tengo 2 WKWebViews en la aplicación, y comparten un WKProcessPool. Comienzo con un grupo de procesos compartido:

WKProcessPool *processPool = [[WKProcessPool alloc] init];

Entonces para cada WKWebView:

WKWebViewConfiguration *theConfiguration = [[WKWebViewConfiguration alloc] init]; theConfiguration.processPool = processPool; self.webView = [[WKWebView alloc] initWithFrame:frame configuration:theConfiguration];

Cuando inicio sesión con el primer WKWebView, y luego algún tiempo después, paso la acción al segundo WKWebView, la sesión se retiene, por lo que las cookies se compartieron con éxito. Sin embargo, cuando relanzo la aplicación, se crea un nuevo grupo de procesos y se destruye la información de la sesión. ¿Hay alguna manera de hacer que la información de la sesión persista a través de un reinicio de la aplicación?


Almacene la información en NSUserDefaults . Al mismo tiempo, si la información de la sesión es muy crítica, es mejor almacenarla en KeyChain .


De hecho, este es un caso porque hay a) un bug que Apple aún no resuelve (creo) yb) depende de las cookies que desee, creo.

No pude probar esto ahora, pero puedo darte algunos consejos:

  1. Obteniendo cookies de NSHTTPCookieStorage.sharedHTTPCookieStorage() . Este parece tener errores, al parecer, las cookies no se guardan de inmediato para que NSHTTPCookieStorage encuentre. People sugiere activar un guardado restableciendo el grupo de procesos, pero no sé si eso funciona de manera confiable. Sin embargo, es posible que quieras probar eso por ti mismo.
  2. El grupo de procesos no es realmente lo que guarda las cookies (aunque define si se comparten como se indica correctamente). La documentación dice que es WKWebsiteDataStore , así que lo buscaría. Al menos obtener las cookies desde allí usando fetchDataRecordsOfTypes:completionHandler: podría ser posible (no estoy seguro de cómo configurarlas, y supongo que no puede simplemente guardar la tienda en los valores predeterminados del usuario por el mismo motivo que para el grupo de procesos).
  3. Si logra obtener las cookies que necesita (o más bien sus valores), pero no puede restaurarlas como supongo que será el caso, mire here (básicamente muestra cómo simplemente preparar la solicitud http con ellas, parte relevante: [request addValue:@"TeskCookieKey1=TeskCookieValue1;TeskCookieKey2=TeskCookieValue2;" forHTTPHeaderField:@"Cookie"] ).
  4. Si todo lo demás falla, revisa this . Sé que proporcionar solo las respuestas a los enlaces no es bueno, pero no puedo copiar todo eso y solo quiero agregarlo para completar el tema.

Una última cosa en general: dije que su éxito también podría depender del tipo de cookie. Esto se debe a que esta respuesta indica que las cookies establecidas por el servidor no son accesibles a través de NSHTTPCookieStorage . No sé si eso es relevante para usted (pero supongo que lo es, ya que probablemente esté buscando una sesión, es decir, una cookie configurada por el servidor, ¿correcto?) Y no sé si esto significa que los otros métodos fallan también.

Si todo lo demás falla, puede considerar guardar las credenciales de los usuarios en algún lugar (llavero, por ejemplo) y reutilizarlas en la próxima aplicación para comenzar a autenticarse automáticamente. Es posible que esto no restaure todos los datos de la sesión, pero considerando que el usuario abandona la aplicación, ¿es realmente deseable? También es posible que ciertos valores se puedan capturar y guardar para su uso posterior utilizando un script inyectado, como se menciona here (obviamente, no para establecerlos al inicio, sino para recuperarlos en algún momento. Debe saber cómo funciona el sitio, por supuesto) .

Espero que al menos pueda señalarle nuevas direcciones para resolver el problema. Parece que no es tan trivial como debería ser (entonces, nuevamente, las cookies de sesión son una cuestión de seguridad relevante, por lo que tal vez ocultarlas fuera de la Aplicación sea una elección consciente por parte de Apple ...).


Después de días de investigación y experimentos, encontré una solución para administrar sesiones en WKWebView. Esta es una solución porque no encontré ninguna otra forma de lograrlo, a continuación se encuentran los pasos:

Primero debe crear métodos para establecer y obtener datos en los valores predeterminados del usuario. Cuando digo datos, significa NSData, aquí están los métodos.

+(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; }

Para mantener la sesión en webview hice mi webview y WKProcessPool singleton.

- (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; }

En ViewDidLoad, verifico si no es la página de inicio de sesión y cargo las cookies en HttpCookieStore desde los valores predeterminados del usuario, por lo que pasará la autenticación o utilizará esas cookies para mantener la sesión.

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

Y, cargar la solicitud.

Ahora, mantendremos sesiones de vista web utilizando cookies, así que en su página de inicio de sesión webview, guarde las cookies de httpCookieStore en los valores predeterminados del usuario en el método 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"]; }

Nota: el método anterior se prueba solo para iOS 11, aunque también he escrito una versión alternativa para versiones inferiores, pero no las probé.

Espero que esto resuelva tus problemas! :)


Finalmente, encontré una solución para administrar sesiones en WKWebView, trabajo bajo swift 4, pero la solución se puede llevar a swift 3 o 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 }}

Crear una extensión para 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) } }}

Luego crea una extensión para nuestro View Controller como este

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

Donde url es URL actual:

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


Llego un poco tarde a la fiesta, pero la gente podría encontrar esto útil. Hay una solución, es un poco molesto, pero por lo que puedo decir, es la única solución que funciona de manera confiable, al menos hasta que Apple arregle sus APIs tontas ...

He pasado unos buenos 3 días tratando de eliminar las cookies almacenadas en la caché de WKWebView hace falta decir que eso no me llevó a ninguna parte ... finalmente WKWebView que solo podía obtener las cookies directamente del servidor.

Lo primero que intenté hacer es obtener todas las cookies con javascript que se ejecutaba dentro de WKWebView y luego pasarlas a WKUserContentController donde solo las almacenaría en UserDefaults . Esto no funcionó desde que mis cookies httponly y aparentemente no puedes obtenerlas con javascript ...

Terminé reparándolo insertando una llamada javascript en la página del lado del servidor (Ruby on Rail en mi caso) con las cookies como parámetro, por ejemplo

sendToDevice("key:value")

La función js anterior es simplemente pasar cookies al dispositivo. Espero que esto ayude a alguien a mantenerse cuerdo ...


Me demoré un poco en responder esto, pero me gustaría agregar algunas ideas a las respuestas existentes. La respuesta ya mencionada here ya proporciona información valiosa sobre Persistencia de cookies en WKWebView. Hay algunas advertencias sin embargo.

  1. WKWebView no funciona bien con NSHTTPCookieStorage , por lo que para iOS 8, 9, 10 tendrá que usar UIWebView.
  2. No es necesario que se WKWebView necesariamente a WKWebView como un singleton, pero sí necesita usar la misma instancia de WKProcessPool cada vez que obtenga las cookies deseadas de nuevo.
  3. Es preferible configurar las cookies primero usando el método setCookie y luego crear una instancia de WKWebView .

También me gustaría resaltar la solución iOS 11+ en Swift.

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

Métodos de ayuda:

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 }

Edición: mencioné que es preferible crear instancias de WKWebView post setCookie . Me encontré con algunos problemas en los que los setCookie finalización de setCookie no fueron llamados la segunda vez que intenté abrir WKWebView . Esto parece ser un error en el WebKit. Por lo tanto, tuve que crear WKWebView instancia de WKWebView primero y luego llamar a setCookie en la configuración. Asegúrese de cargar la URL solo después de que todas las llamadas a setCookie hayan regresado.


WKWebView ajusta a NSCoding , por lo que puede usar NSCoder para decodificar / codificar su webView, y almacenarlo en otro lugar, como NSUserDefaults .

//return data to store somewhere NSData* data = [NSKeyedArchiver archivedDataWithRootObject:self.webView];/ self.webView = [NSKeyedUnarchiver unarchiveObjectWithData:data];