WKWebView stockage persistant des Cookies

j'utilise un WKWebView dans mon application iPhone native, sur un site web qui permet la Connexion/Inscription, et stocke les informations de session dans des cookies. J'essaie de trouver comment conserver les informations du cookie de façon constante, donc quand l'application redémarre, l'Utilisateur a toujours sa session web disponible.

j'ai 2 WKWebViews dans l'application, et ils partagent un WKProcessPool. Je commence par un pool de processus partagé:

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

puis pour chaque WKWebView:

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

quand je me connecte en utilisant le premier WKWebView, et puis quelque temps plus tard passer l'action au 2nd WKWebView, la session est conservée, de sorte que les cookies ont été partagés avec succès. Cependant, lorsque je relance l'application, un nouveau pool de processus est créé et les informations de session sont détruites. Est-il possible d'obtenir les informations de session, qui persistent à travers une application redémarrer?

21
demandé sur haplo1384 2016-09-29 16:42:25

6 réponses

il s'agit en fait d'un bien de réflexion parce qu'il y a a) quelque bug qui n'est toujours pas résolu par Apple (je pense) et B) dépend de ce que les cookies que vous voulez, je pense.

je n'ai pas pu tester cela, mais je peux vous donner quelques conseils:

  1. Obtention des cookies de NSHTTPCookieStorage.sharedHTTPCookieStorage() . Celui-ci semble bogué, apparemment les cookies ne sont pas immédiatement enregistrés pour NSHTTPCookieStorage pour les trouver. les Gens suggère de déclencher une sauvegarde en réinitialisant le pool de processus, mais je ne sais pas si cela fonctionne de manière fiable. Vous voudrez peut-être essayer cela pour vous-même, cependant.
  2. le pool de processus n'est pas vraiment ce qui sauve les cookies (bien qu'il définisse s'ils sont partagés comme vous l'avez correctement déclaré). La documentation dit que c'est WKWebsiteDataStore , donc j'ai vérifié. Au moins obtenir les cookies à partir de là en utilisant fetchDataRecordsOfTypes:completionHandler: pourrait être possible (pas sûr comment les configurer, cependant, et je suppose vous ne pouvez pas simplement sauver le magasin dans les valeurs par défaut de l'utilisateur pour la même raison que pour le pool de processus).
  3. si vous parvenez à obtenir les cookies dont vous avez besoin (ou plutôt leurs valeurs), mais ne pouvez pas les restaurer comme je suppose que ce sera le cas, regardez ici (en gros, il montre comment préparer simplement la requête httprequest avec eux déjà, partie pertinente: [request addValue:@"TeskCookieKey1=TeskCookieValue1;TeskCookieKey2=TeskCookieValue2;" forHTTPHeaderField:@"Cookie"] ).
  4. si tout le reste échoue, cocher de cette . Je sais fournir seulement des réponses de lien seulement n'est pas bon, mais je ne peux pas copier tout cela et je veux juste l'Ajouter pour le plaisir de l'exhaustivité.

Une dernière chose: j'ai dit que votre succès pourrait aussi dépendre du type de cookie. C'est parce que cette réponse indique que les cookies définis par le serveur ne sont pas accessibles via NSHTTPCookieStorage . Je ne sais pas si c'est pertinent pour vous (mais je suppose que oui, puisque vous êtes probablement à la recherche d'une session, i.e. serveur de cookie ensemble, correct?) et je ne sais pas si cela signifie que les autres méthodes échouent.

si tout le reste échoue, vous pourriez envisager de sauvegarder les informations d'identification des utilisateurs quelque part (porte-clés, par exemple) et de les réutiliser sur le prochain démarrage de l'application pour autoriser automatiquement. Cela pourrait ne pas restaurer toutes les données de session, mais en considérant l'Utilisateur a quitté l'application qui est peut-être réellement souhaitable? Aussi peut-être certaines valeurs peuvent être capturées et sauvegardées pour une utilisation ultérieure en utilisant un script injecté, comme mentionné ici (évidemment pas pour les mettre au départ, mais peut-être les récupérer à un moment donné. Vous devez savoir comment le site fonctionne alors, bien sûr).

j'espère que cela pourrait au moins vous indiquer de nouvelles directions pour résoudre le problème. Ce n'est pas aussi insignifiant que cela devrait l'être, il semble (encore une fois, les cookies de session sont un peu une chose importante de sécurité, donc peut-être les cacher loin de l'application est un choix de conception conscient par Apple...).

12
répondu Gero 2017-05-23 12:17:32

je suis un peu en retard à la fête, mais les gens pourraient trouver cela utile. Il ya une solution de contournement, il est un peu ennuyeux, mais autant que je peux dire que c'est la seule solution qui fonctionne de manière fiable, au moins jusqu'à ce que apple réparer leur stupide API...

j'ai passé 3 bons jours à essayer de sortir les cookies du WKWebView inutile de dire que ça m'a mené nulle part... finalement, j'ai libéré que je pouvais juste obtenir les cookies directement du serveur.

le la première chose que j'ai essayé de faire est d'obtenir tous les cookies avec javascript qui fonctionnait dans le WKWebView et puis les passer au WKUserContentController où je les stockerais juste à UserDefaults . Cela n'a pas fonctionné depuis mes cookies où httponly et apparemment vous ne pouvez pas obtenir ceux avec javascript...

j'ai fini par le corriger en insérant un appel javascript dans la page Côté Serveur (Ruby on Rail dans mon cas) avec les cookies comme paramètre, par exemple

sendToDevice("key:value")

la fonction JS ci-dessus ne fait que transmettre des cookies à l'appareil. Espérons que cela permettra à quelqu'un de rester sain d'esprit...

3
répondu Dovydas Rupšys 2016-11-24 23:45:16

après des jours de recherches et d'expériences, j'ai trouvé une solution pour gérer les sessions dans WKWebView, c'est un travail autour parce que je n'ai pas trouvé d'autre moyen d'atteindre ceci, voici les étapes:

tout d'abord, vous avez besoin de créer des méthodes pour définir et obtenir des données par défaut de l'utilisateur, quand je dis données il signifie NSData, voici les méthodes.

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

pour maintenir la session sur webview j'ai fait mon WebView et 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;
}

dans ViewDidLoad, je vérifie si ce n'est pas la page de connexion et je charge les cookies dans HttpCookieStore à partir des paramètres par défaut de L'utilisateur afin qu'il passe l'authentification ou utilise ces cookies pour maintenir la session.

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

et, charger la requête.

maintenant, nous allons maintenir les sessions webview en utilisant des cookies, donc sur votre page de connexion webview, enregistrez les cookies à partir de httpCookieStore en mode utilisateur par défaut dans la méthode viewdidisappear.

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

Note: la méthode ci-dessus est testée pour iOS 11 seulement, bien que j'ai écrit repli pour les versions inférieures aussi, mais ne les a pas testées.

Espère que cela résout vos problèmes !!! :)

2
répondu Harish Pathak 2018-04-04 12:53:31

je suis un peu en retard pour répondre à cela, mais je voudrais ajouter quelques idées aux réponses existantes. La réponse déjà mentionnée ici fournit déjà de précieux renseignements sur la persistance des témoins dans WKWebView. Il y a quelques réserves cependant.

  1. WKWebView ne fonctionne pas bien avec NSHTTPCookieStorage , donc pour iOS 8, 9, 10 vous devrez utiliser UIWebView.
  2. vous ne devez pas nécessairement tenir sur le WKWebView comme un singleton mais vous devez utiliser la même instance de WKProcessPool chaque fois pour obtenir les cookies désirés à nouveau.
  3. il est préférable de configurer les cookies d'abord en utilisant le setCookie méthode et puis instancier le WKWebView .

je voudrais également souligner la Solution iOS 11+ dans 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éthodes d'aide:

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
}

Edit: Je avait mentionné qu'il est préférable d'instancier WKWebView post setCookie appels. J'ai rencontré des problèmes dans lesquels les manipulateurs d'achèvement setCookie ne se faisaient pas appeler la deuxième fois que j'ai essayé d'ouvrir le WKWebView . Cela semble être un bug dans le WebKit. Par conséquent, j'ai dû instancier WKWebView d'abord et ensuite appeler setCookie sur la configuration. Assurez-vous de ne charger l'URL qu'après le retour de tous les appels setCookie .

1
répondu jarora 2018-09-18 01:58:03

WKWebView est conforme à NSCoding , vous pouvez donc utiliser NSCoder pour décoder/encoder votre webView ,et le stocker ailleurs ,comme NSUserDefaults .

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

self.webView = [NSKeyedUnarchiver unarchiveObjectWithData:data];
0
répondu wj2061 2016-10-03 16:27:03

Stocker les informations dans NSUserDefaults . En même temps si l'information de session est très critique, il est préférable de la stocker dans KeyChain .

-1
répondu Sachin Vas 2016-09-29 15:55:22