Puis-je configurer les cookies à utiliser par un WKWebView?

135

J'essaie de faire passer une application existante de UIWebViewà WKWebView. L'application actuelle gère la connexion / session des utilisateurs en dehors de webviewet définit les éléments cookiesrequis pour l'authentification dans le NSHTTPCookieStore. Malheureusement, new WKWebViewn'utilise pas le cookiesfichier NSHTTPCookieStorage. Y a-t-il un autre moyen d'y parvenir?

Col
la source

Réponses:

186

Modifier uniquement pour iOS 11+

Utilisez WKHTTPCookieStore :

let cookie = HTTPCookie(properties: [
    .domain: "example.com",
    .path: "/",
    .name: "MyCookieName",
    .value: "MyCookieValue",
    .secure: "TRUE",
    .expires: NSDate(timeIntervalSinceNow: 31556926)
])! 

webView.configuration.websiteDataStore.httpCookieStore.setCookie(cookie)

Puisque vous les récupérez de HTTPCookeStorage, vous pouvez faire ceci:

let cookies = HTTPCookieStorage.shared.cookies ?? []
for cookie in cookies {
    webView.configuration.websiteDataStore.httpCookieStore.setCookie(cookie)
}

Ancienne réponse pour iOS 10 et inférieur

Si vous souhaitez que vos cookies soient définis lors de la demande de chargement initiale, vous pouvez les définir sur NSMutableURLRequest. Comme les cookies ne sont qu'un en-tête de requête spécialement formaté, cela peut être réalisé comme suit:

WKWebView * webView = /*set up your webView*/
NSMutableURLRequest * request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"http://example.com/index.html"]];
[request addValue:@"TeskCookieKey1=TeskCookieValue1;TeskCookieKey2=TeskCookieValue2;" forHTTPHeaderField:@"Cookie"];
// use stringWithFormat: in the above line to inject your values programmatically
[webView loadRequest:request];

Si vous avez besoin de requêtes AJAX ultérieures sur la page pour que leurs cookies soient définis, cela peut être réalisé en utilisant simplement WKUserScript pour définir les valeurs par programme via javascript au démarrage du document comme ceci:

WKUserContentController* userContentController = WKUserContentController.new;
WKUserScript * cookieScript = [[WKUserScript alloc] 
    initWithSource: @"document.cookie = 'TeskCookieKey1=TeskCookieValue1';document.cookie = 'TeskCookieKey2=TeskCookieValue2';"
    injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:NO];
// again, use stringWithFormat: in the above line to inject your values programmatically
[userContentController addUserScript:cookieScript];
WKWebViewConfiguration* webViewConfig = WKWebViewConfiguration.new;
webViewConfig.userContentController = userContentController;
WKWebView * webView = [[WKWebView alloc] initWithFrame:CGRectMake(/*set your values*/) configuration:webViewConfig];

La combinaison de ces deux techniques devrait vous donner suffisamment d'outils pour transférer les valeurs des cookies de Native App Land vers Web View Land. Vous pouvez trouver plus d'informations sur l' API javascript cookie sur la page de Mozilla si vous avez besoin de cookies plus avancés.

Oui, ça craint qu'Apple ne prenne pas en charge la plupart des subtilités d'UIWebView . Je ne sais pas s'ils les soutiendront un jour, mais j'espère qu'ils le feront bientôt. J'espère que cela t'aides!

mattr
la source
1
Où est le meilleur endroit pour injecter des cookies pour les demandes ultérieures? Par exemple, le chargement initial de la page est couvert dans la réponse ci-dessus, mais que se passe-t-il s'il existe des liens sur la page qui mènent également au même domaine et qui ont également besoin des mêmes cookies injectés dans la demande? didStartProvisionalNavigation?
Mason G. Zhwiti
1
désolé cela ne fonctionne pas pour vous. Dans mon esprit, tant que les domaines sont les mêmes, il ne devrait y avoir aucun problème. Pouvez-vous vérifier le code que le lien pointe vers le même domaine à partir duquel vous avez chargé la demande? En outre, les cookies peuvent également être contraints à un "chemin" spécifique. Peut-être que cela cause des problèmes?
mattr
11
Notez que la technique javascript pour définir les cookies ne fonctionnera pas pour les cookies «HTTP uniquement».
Ahmed Nasser
1
La méthode ci-dessus fonctionne très bien ... mais je pourrais voir des cookies dupliqués dans les appels AJAX suivants (dupliqués une seule fois).
Durga Vundavalli
1
@ Axel92Dev une solution de contournement serait de s'assurer que la première requête faite à partir de votre webview à votre serveur obtienne une réponse qui indique explicitement à la webview de définir à nouveau les cookies avec l'indicateur HTTPOnly (c'est-à-dire: définissez à nouveau les cookies dans la réponse). Vous pouvez créer une API spéciale dans ce seul but lors de l'initialisation de la vue Web, puis utiliser la vue Web normalement en cas de succès.
Ahmed Nasser
64

Après avoir joué avec cette réponse (qui était extrêmement utile :), nous avons dû faire quelques changements:

  • Nous avons besoin de vues Web pour traiter plusieurs domaines sans divulguer d'informations sur les cookies privés entre ces domaines
  • Nous en avons besoin pour honorer les cookies sécurisés
  • Si le serveur modifie la valeur d'un cookie, nous voulons que notre application le sache dans NSHTTPCookieStorage
  • Si le serveur modifie la valeur d'un cookie, nous ne voulons pas que nos scripts la réinitialisent à sa valeur d'origine lorsque vous suivez un lien / AJAX, etc.

Nous avons donc modifié notre code pour être ceci;

Créer une demande

NSMutableURLRequest *request = [originalRequest mutableCopy];
NSString *validDomain = request.URL.host;
const BOOL requestIsSecure = [request.URL.scheme isEqualToString:@"https"];

NSMutableArray *array = [NSMutableArray array];
for (NSHTTPCookie *cookie in [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies]) {
    // Don't even bother with values containing a `'`
    if ([cookie.name rangeOfString:@"'"].location != NSNotFound) {
        NSLog(@"Skipping %@ because it contains a '", cookie.properties);
        continue;
    }

    // Is the cookie for current domain?
    if (![cookie.domain hasSuffix:validDomain]) {
        NSLog(@"Skipping %@ (because not %@)", cookie.properties, validDomain);
        continue;
    }

    // Are we secure only?
    if (cookie.secure && !requestIsSecure) {
        NSLog(@"Skipping %@ (because %@ not secure)", cookie.properties, request.URL.absoluteString);
        continue;
    }

    NSString *value = [NSString stringWithFormat:@"%@=%@", cookie.name, cookie.value];
    [array addObject:value];
}

NSString *header = [array componentsJoinedByString:@";"];
[request setValue:header forHTTPHeaderField:@"Cookie"];

// Now perform the request...

Cela garantit que la première demande a les bons cookies définis, sans envoyer de cookies du stockage partagé qui sont pour d'autres domaines, et sans envoyer de cookies sécurisés dans une demande non sécurisée.

Traiter d'autres demandes

Nous devons également nous assurer que les autres demandes ont les cookies définis. Cela se fait à l'aide d'un script qui s'exécute lors du chargement du document qui vérifie s'il existe un ensemble de cookies et, dans le cas contraire, définissez-le sur la valeur dans NSHTTPCookieStorage.

// Get the currently set cookie names in javascriptland
[script appendString:@"var cookieNames = document.cookie.split('; ').map(function(cookie) { return cookie.split('=')[0] } );\n"];

for (NSHTTPCookie *cookie in [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies]) {
    // Skip cookies that will break our script
    if ([cookie.value rangeOfString:@"'"].location != NSNotFound) {
        continue;
    }

    // Create a line that appends this cookie to the web view's document's cookies
    [script appendFormat:@"if (cookieNames.indexOf('%@') == -1) { document.cookie='%@'; };\n", cookie.name, cookie.wn_javascriptString];
}

WKUserContentController *userContentController = [[WKUserContentController alloc] init];
WKUserScript *cookieInScript = [[WKUserScript alloc] initWithSource:script
                                                      injectionTime:WKUserScriptInjectionTimeAtDocumentStart
                                                   forMainFrameOnly:NO];
[userContentController addUserScript:cookieInScript];

...

// Create a config out of that userContentController and specify it when we create our web view.
WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];
config.userContentController = userContentController;

self.webView = [[WKWebView alloc] initWithFrame:webView.bounds configuration:config];

Gérer les changements de cookies

Nous devons également faire en sorte que le serveur modifie la valeur d'un cookie. Cela signifie ajouter un autre script pour rappeler la vue Web que nous créons pour mettre à jour notre NSHTTPCookieStorage.

WKUserScript *cookieOutScript = [[WKUserScript alloc] initWithSource:@"window.webkit.messageHandlers.updateCookies.postMessage(document.cookie);"
                                                       injectionTime:WKUserScriptInjectionTimeAtDocumentStart
                                                    forMainFrameOnly:NO];
[userContentController addUserScript:cookieOutScript];

[userContentController addScriptMessageHandler:webView
                                          name:@"updateCookies"];

et implémenter la méthode déléguée pour mettre à jour tous les cookies qui ont changé, en s'assurant que nous ne mettons à jour que les cookies du domaine actuel!

- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message {
    NSArray<NSString *> *cookies = [message.body componentsSeparatedByString:@"; "];
    for (NSString *cookie in cookies) {
        // Get this cookie's name and value
        NSArray<NSString *> *comps = [cookie componentsSeparatedByString:@"="];
        if (comps.count < 2) {
            continue;
        }

        // Get the cookie in shared storage with that name
        NSHTTPCookie *localCookie = nil;
        for (NSHTTPCookie *c in [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookiesForURL:self.wk_webView.URL]) {
            if ([c.name isEqualToString:comps[0]]) {
                localCookie = c;
                break;
            }
        }

        // If there is a cookie with a stale value, update it now.
        if (localCookie) {
            NSMutableDictionary *props = [localCookie.properties mutableCopy];
            props[NSHTTPCookieValue] = comps[1];
            NSHTTPCookie *updatedCookie = [NSHTTPCookie cookieWithProperties:props];
            [[NSHTTPCookieStorage sharedHTTPCookieStorage] setCookie:updatedCookie];
        }
    }
}

Cela semble résoudre nos problèmes de cookies sans que nous ayons à traiter différemment chaque endroit où nous utilisons WKWebView. Nous pouvons maintenant simplement utiliser ce code comme une aide pour créer nos vues Web et il se met NSHTTPCookieStorageà jour de manière transparente pour nous.


EDIT: Il s'avère que j'ai utilisé une catégorie privée sur NSHTTPCookie - voici le code:

- (NSString *)wn_javascriptString {
    NSString *string = [NSString stringWithFormat:@"%@=%@;domain=%@;path=%@",
                        self.name,
                        self.value,
                        self.domain,
                        self.path ?: @"/"];

    if (self.secure) {
        string = [string stringByAppendingString:@";secure=true"];
    }

    return string;
}
deanWombourne
la source
6
J'ai enveloppé votre code dans une sous-classe de WKWebView. N'hésitez pas à le vérifier sur github.com/haifengkao/YWebView
Hai Feng Kao
Que faire si vos cookies contiennent des signes = dans la valeur? Cela fonctionnerait-il?
iOSAddicted
@iOSAddicted je pense que oui. Si votre valeur était, a=bvous vous retrouveriez avec la chaîne de cookie name=a=b;domain=.example.com;path=/- je crois que la norme se ;divise puis se divise en premier = dans la paire clé = valeur. Je
testerais
votre réponse m'a beaucoup aidé, je voudrais cependant ajouter quelque chose à votre message, il y a plusieurs risques lors de l'utilisation de votre méthode de mise à jour, certains frameworks JS peuvent créer des cookies qui ont le même nom mais un domaine différent, et si vous essayez de le mettre à jour en utilisant les méthodes js, vous avez un risque élevé de mettre à jour un cookie avec une valeur erronée. De plus, pour nous, la chaîne de cookies js a dû être dépouillée de son drapeau sécurisé, car notre serveur effectue des redirections désagréables entre http et https, empêchant ainsi les cookies sécurisés d'être présents dans certaines pages dans certains cas extrêmes.
RicardoDuarte
Je pense en fait que la société avec laquelle j'étais lorsque j'ai écrit ceci a dû lui ajouter une protection de domaine après sa mise en ligne. Nous n'avons jamais (afaik) rencontré le problème sécurisé / non sécurisé - cela ressemble à un cauchemar!
deanWombourne
42

Les cookies doivent être définis sur la configuration avant la WKWebViewcréation de. Sinon, même avec WKHTTPCookieStorele setCookiegestionnaire de complétion de, les cookies ne seront pas synchronisés de manière fiable avec la vue Web. Cela revient à cette ligne de la documentation surWKWebViewConfiguration

@NSCopying var configuration: WKWebViewConfiguration { get }

C'est en @NSCopyingquelque sorte une copie profonde. L'implémentation me dépasse, mais le résultat final est qu'à moins de définir des cookies avant d'initialiser la vue Web, vous ne pouvez pas compter sur la présence des cookies. Cela peut compliquer l'architecture de l'application, car l'initialisation d'une vue devient un processus asynchrone. Tu finiras avec quelque chose comme ça

extension WKWebViewConfiguration {
    /// Async Factory method to acquire WKWebViewConfigurations packaged with system cookies
    static func cookiesIncluded(completion: @escaping (WKWebViewConfiguration?) -> Void) {
        let config = WKWebViewConfiguration()
        guard let cookies = HTTPCookieStorage.shared.cookies else {
            completion(config)
            return
        }
        // Use nonPersistent() or default() depending on if you want cookies persisted to disk
        // and shared between WKWebViews of the same app (default), or not persisted and not shared
        // across WKWebViews in the same app.
        let dataStore = WKWebsiteDataStore.nonPersistent()
        let waitGroup = DispatchGroup()
        for cookie in cookies {
            waitGroup.enter()
            dataStore.httpCookieStore.setCookie(cookie) { waitGroup.leave() }
        }
        waitGroup.notify(queue: DispatchQueue.main) {
            config.websiteDataStore = dataStore
            completion(config)
        }
    }
}

puis pour l'utiliser quelque chose comme

override func loadView() {
    view = UIView()
    WKWebViewConfiguration.cookiesIncluded { [weak self] config in
        let webView = WKWebView(frame: .zero, configuration: webConfiguration)
        webView.load(request)
        self.view = webView
    }
}

L'exemple ci-dessus reporte la création de la vue jusqu'au dernier moment possible, une autre solution serait de créer la configuration ou la vue Web bien à l'avance et de gérer la nature asynchrone avant la création d'un contrôleur de vue.

Une dernière remarque: une fois que vous avez créé cette vue Web, vous l'avez libérée dans la nature, vous ne pouvez pas ajouter plus de cookies sans utiliser les méthodes décrites dans cette réponse . Vous pouvez cependant utiliser l' WKHTTPCookieStoreObserverAPI pour au moins observer les modifications apportées aux cookies. Ainsi, si un cookie de session est mis à jour dans la vue Web, vous pouvez mettre à jour manuellement le système HTTPCookieStorageavec ce nouveau cookie si vous le souhaitez.

Pour en savoir plus, passez à 18h00 à ce chargement de contenu Web personnalisé de la session WWDC 2017 . Au début de cette session, il existe un exemple de code trompeur qui omet le fait que la vue Web doit être créée dans le gestionnaire d'achèvement.

cookieStore.setCookie(cookie!) {
    webView.load(loggedInURLRequest)
}

La démo en direct à 18h00 clarifie cela.

Modifier À partir de Mojave Beta 7 et iOS 12 Beta 7 au moins, je constate un comportement beaucoup plus cohérent avec les cookies. La setCookie(_:)méthode semble même autoriser la configuration de cookies après la WKWebViewcréation de. J'ai cependant trouvé important de ne pas toucher du processPooltout à la variable. La fonctionnalité de configuration des cookies fonctionne mieux lorsqu'aucun pool supplémentaire n'est créé et lorsque cette propriété est laissée seule. Je pense qu'il est prudent de dire que nous avons eu des problèmes en raison de certains bogues dans WebKit.

Nteissler
la source
On dirait que la gestion / configuration des cookies est plus fiable dans Mojave 10.14 beta 3 et iOS 12 beta 3
nteissler
6
Réponse très approfondie et sous-estimée
Nicolás Carrasco
1
J'ai toujours ce problème dans iOS 12 avec un WKWebView déjà chargé. Parfois, setCookie () sera en fait synchronisé avec WKWebView tout de suite, parfois cela ne rendra pas la gestion quelque peu sporadique
bmjohns
J'ai toujours vu des problèmes depuis que le radar a été déclaré corrigé, mais beaucoup moins fréquemment. À quelle fréquence voyez-vous l'échec du cookie? Si vous avez un projet reproductible, assez petit, je vous recommande vraiment de soumettre un bug WebKit ici: webkit.org/reporting-bugs~~V~~aux~~singular~~2nd Vous pouvez également gazouiller Brady Eidson (bien) un architecte WebKit chez Apple qui est très sensible à ce genre de rapports et bogues.
nteissler
c'est la bonne réponse - pas besoin de traduire manuellement les cookies comme champs d'en-tête dans chaque URLRequest, c'est juste que setCookie () doit être utilisé comme décrit ici.
Guillaume Laurent
25

travaille pour moi

func webView(webView: WKWebView, decidePolicyForNavigationAction navigationAction: WKNavigationAction, decisionHandler: (WKNavigationActionPolicy) -> Void) {
    let headerFields = navigationAction.request.allHTTPHeaderFields
    var headerIsPresent = contains(headerFields?.keys.array as! [String], "Cookie")

    if headerIsPresent {
        decisionHandler(WKNavigationActionPolicy.Allow)
    } else {
        let req = NSMutableURLRequest(URL: navigationAction.request.URL!)
        let cookies = yourCookieData
        let values = NSHTTPCookie.requestHeaderFieldsWithCookies(cookies)
        req.allHTTPHeaderFields = values
        webView.loadRequest(req)

        decisionHandler(WKNavigationActionPolicy.Cancel)
    }
}
user3589213
la source
Hack génial, en particulier à quel point iOS intransigeant consiste à remplacer un cookie dans un WKWebView existant. Le seul problème est que la précédente WKNavigationKey est devenue obsolète. Un autre code attend peut-être en vain l'ancien.
BaseZen
2
est-ce correct? J'apprécie que cela puisse fonctionner dans certaines circonstances. Cependant, la responsabilité de cette méthode déléguée - decidePolicyForNavigationAction - est de décider de la politique; de ne pas charger réellement la requête. Cela a été lancé précédemment. Dans quel cas, cela ne provoque-t-il pas le chargement de la requête deux fois?
Max MacLeod
2
@MaxMacLeod Dans la elsecondition avec laquelle il appelle la decisionHandlerfermeture pour .cancelque le webviewne charge pas réellement la requête initiale. Une fois que le loadRequestest appelé dans la elsecondition, cette méthode déléguée sera à nouveau appelée pour cette demande et elle passera dans la ifcondition car l'en- Cookietête sera présent.
halil_g
2
Bien que cela ne fonctionne pas lorsque la demande initiale a déjà des cookies définis, car cela n'entrera jamais dans la elsecondition.
halil_g
Notez que c'est 1) Ne fonctionne pas pour toutes les situations - par exemple, le cas où la vue Web charge des cadres 2) Pas sûr - il pourrait envoyer un cookie avec des informations sensibles à une URL tierce
Peter Prokop
20

Voici ma version de la solution Mattrs dans Swift pour injecter tous les cookies de HTTPCookieStorage. Cela a été fait principalement pour injecter un cookie d'authentification pour créer une session utilisateur.

public func setupWebView() {
    let userContentController = WKUserContentController()
    if let cookies = HTTPCookieStorage.shared.cookies {
        let script = getJSCookiesString(for: cookies)
        let cookieScript = WKUserScript(source: script, injectionTime: .atDocumentStart, forMainFrameOnly: false)
        userContentController.addUserScript(cookieScript)
    }
    let webViewConfig = WKWebViewConfiguration()
    webViewConfig.userContentController = userContentController

    self.webView = WKWebView(frame: self.webViewContainer.bounds, configuration: webViewConfig)
}

///Generates script to create given cookies
public func getJSCookiesString(for cookies: [HTTPCookie]) -> String {
    var result = ""
    let dateFormatter = DateFormatter()
    dateFormatter.timeZone = TimeZone(abbreviation: "UTC")
    dateFormatter.dateFormat = "EEE, d MMM yyyy HH:mm:ss zzz"

    for cookie in cookies {
        result += "document.cookie='\(cookie.name)=\(cookie.value); domain=\(cookie.domain); path=\(cookie.path); "
        if let date = cookie.expiresDate {
            result += "expires=\(dateFormatter.stringFromDate(date)); "
        }
        if (cookie.secure) {
            result += "secure; "
        }
        result += "'; "
    }
    return result
}
Misha
la source
mieux vaut ajouter cette ligne pour s'assurer que le formatage des paramètres régionaux est correct:dateFormatter.locale = NSLocale(localeIdentifier: "en_US_POSIX")
bubuxu
où appeler ça?
markhorrocks
Cela a bien fonctionné pour moi dans Swift 4 (avec des ajustements mineurs)
Frédéric Adda
Cela fonctionne très bien pour moi, mais seulement la deuxième fois que je visite le site (la première fois que les cookies ne sont pas définis) - quelqu'un est tombé sur cela?
MrChrisBarker
Le premier chargement donne une erreur, le deuxième travail de chargement: (quel pourrait être le problème?
Shauket Sheikh
10

définir un cookie

self.webView.evaluateJavaScript("document.cookie='access_token=your token';domain='your domain';") { (data, error) -> Void in
        self.webView.reload()
}

supprimer le cookie

self.webView.evaluateJavaScript("document.cookie='access_token=';domain='your domain';") { (data, error) -> Void in
        self.webView.reload()
}
cycDroid
la source
9

Mise à jour de Swift 3:

func webView(_ webView: WKWebView, decidePolicyFor navigationResponse: WKNavigationResponse, decisionHandler: @escaping (WKNavigationResponsePolicy) -> Void) {
    if let urlResponse = navigationResponse.response as? HTTPURLResponse,
       let url = urlResponse.url,
       let allHeaderFields = urlResponse.allHeaderFields as? [String : String] {
       let cookies = HTTPCookie.cookies(withResponseHeaderFields: allHeaderFields, for: url)
       HTTPCookieStorage.shared.setCookies(cookies , for: urlResponse.url!, mainDocumentURL: nil)
       decisionHandler(.allow)
    }
}
Parekh profond
la source
1
Bonjour, pouvez-vous également ajouter du code pour obtenir des cookies HTTPCookieStorage.shared?
markhorrocks
C'est le seul moyen que j'ai obtenu de WKWebView pour ajouter des cookies à chaque demande faite par le webview
Chicowitz
S'il contient un cookie httponly en réponse, vous ne pouvez pas obtenir la valeur des cookies de cette manière.
brain
1
cela ne fait que remettre les cookies sur le stockage httpcookies où se trouve le code qui configure les cookies pour wkwebview?
Shauket Sheikh
8

Après avoir parcouru diverses réponses ici et sans succès, j'ai parcouru la documentation WebKit et suis tombé sur la requestHeaderFieldsméthode statique HTTPCookie, qui convertit un tableau de cookies dans un format adapté à un champ d'en-tête. En combinant cela avec la vision de mattr de la mise à jourURLRequest avant de le charger avec les en-têtes de cookie m'a permis de franchir la ligne d'arrivée.

Swift 4.1, 4.2, 5.0:

var request = URLRequest(url: URL(string: "https://example.com/")!)
let headers = HTTPCookie.requestHeaderFields(with: cookies)
for (name, value) in headers {
    request.addValue(value, forHTTPHeaderField: name)
}

let webView = WKWebView(frame: self.view.frame)
webView.load(request)

Pour rendre cela encore plus simple, utilisez une extension:

extension WKWebView {
    func load(_ request: URLRequest, with cookies: [HTTPCookie]) {
        var request = request
        let headers = HTTPCookie.requestHeaderFields(with: cookies)
        for (name, value) in headers {
            request.addValue(value, forHTTPHeaderField: name)
        }

        load(request)
    }
}

Maintenant, cela devient simplement:

let request = URLRequest(url: URL(string: "https://example.com/")!)
let webView = WKWebView(frame: self.view.frame)
webView.load(request, with: cookies)

Cette extension est également disponible dans LionheartExtensions si vous souhaitez simplement une solution intégrée. À votre santé!

Dan Loewenherz
la source
1
@ShauketSheikh hmm, dans quelles situations cela ne fonctionne-t-il pas?
Dan Loewenherz
J'ai testé en utilisant le simulateur ios 8, il ne semble pas envoyer de cookies. je l'ai vérifié deux fois.
Shauket Sheikh le
J'ai posté ma réponse, vous pouvez essayer @Dan
Shauket Sheikh
7

Sous iOS 11, vous pouvez gérer les cookies maintenant :), voir cette session: https://developer.apple.com/videos/play/wwdc2017/220/

entrez la description de l'image ici

Jacky
la source
2
@ShobhakarTiwari pourquoi? des changements se produisent-ils dans la version officielle d'iOS11?
Jacky
La meilleure façon de procéder si vous ne prenez en charge que iOS 11 et les versions ultérieures, si vous devez prendre en charge les versions précédentes, utilisez JavaScript avant le chargement de la page.
PashaN
Cela fonctionne pour moi, sauf pour le fait que parfois la méthode setcookie NE lance PAS son gestionnaire d'achèvement, ce qui signifie que parfois ma page Web ne se charge pas - ne se produit que sur l'appareil, se produit la 3e / 4e / 5e fois la fermeture et la réouverture la vue Web, et après que cela se soit produit une fois, cela continue jusqu'à ce que je réinitialise l'application - quelqu'un a également rencontré cela?
Binya Koatz
5

La raison derrière la publication de cette réponse est que j'ai essayé de nombreuses solutions mais personne ne fonctionne correctement, la plupart de la réponse ne fonctionne pas dans le cas où vous devez définir un cookie pour la première fois et que le cookie de résultat ne se synchronise pas la première fois, veuillez utiliser cette solution, cela fonctionne pour les deux iOS> = 11.0 <= iOS 11 à 8.0, fonctionne également avec la synchronisation des cookies pour la première fois.

Pour iOS> = 11,0 - Swift 4.2

Obtenez des cookies http et définissez-les dans le magasin de cookies wkwebview de cette manière, il est très difficile de charger votre demande dans wkwebview , vous devez envoyer une demande de chargement lorsque les cookies seront complètement définis, voici la fonction que j'ai écrite.

Appelez la fonction avec la fermeture à la fin que vous appelez load webview. Pour info, cette fonction ne gère que iOS> = 11.0

self.WwebView.syncCookies {
    if let request = self.request {
       self.WwebView.load(request)
    }
}

Voici l'implémentation de la fonction syncCookies .

func syncCookies(completion:@escaping ()->Void) {

if #available(iOS 11.0, *) {

      if let yourCookie = "HERE_YOUR_HTTP_COOKIE_OBJECT" {
        self.configuration.websiteDataStore.httpCookieStore.setCookie(yourCookie, completionHandler: {
              completion()
        })
     }
  } else {
  //Falback just sent 
  completion()
}
}

Pour iOS 8 à iOS 11

vous devez configurer certaines choses supplémentaires dont vous avez besoin pour définir deux cookies temporels un en utilisant WKUserScript et n'oubliez pas d'ajouter également des cookies dans la demande, sinon votre cookie ne se synchronise pas la première fois et vous verrez que votre page ne se charge pas correctement la première fois. c'est le diable que j'ai trouvé pour prendre en charge les cookies pour iOS 8.0

avant de créer un objet Wkwebview.

func setUpWebView() {

    let userController: WKUserContentController = WKUserContentController.init()

    if IOSVersion.SYSTEM_VERSION_LESS_THAN(version: "11.0") {
        if let cookies = HTTPCookieStorage.shared.cookies {
            if let script = getJSCookiesString(for: cookies) {
                cookieScript = WKUserScript(source: script, injectionTime: .atDocumentStart, forMainFrameOnly: false)
                userController.addUserScript(cookieScript!)
            }
        }
    }

    let webConfiguration = WKWebViewConfiguration()
    webConfiguration.processPool = BaseWebViewController.processPool


    webConfiguration.userContentController = userController


    let customFrame = CGRect.init(origin: CGPoint.zero, size: CGSize.init(width: 0.0, height: self.webContainerView.frame.size.height))
    self.WwebView = WKWebView (frame: customFrame, configuration: webConfiguration)
    self.WwebView.translatesAutoresizingMaskIntoConstraints = false
    self.webContainerView.addSubview(self.WwebView)
    self.WwebView.uiDelegate = self
    self.WwebView.navigationDelegate = self
    self.WwebView.allowsBackForwardNavigationGestures = true // A Boolean value indicating whether horizontal swipe gestures will trigger back-forward list navigations
    self.WwebView.addObserver(self, forKeyPath: #keyPath(WKWebView.estimatedProgress), options: .new, context: nil)


 self.view.addConstraint(NSLayoutConstraint(item: WwebView, attribute: .trailing, relatedBy: .equal, toItem: self.webContainerView, attribute: .trailing, multiplier: 1, constant: 0))
    self.view.addConstraint(NSLayoutConstraint(item: WwebView, attribute: .leading, relatedBy: .equal, toItem: self.webContainerView, attribute: .leading, multiplier: 1, constant: 0))
    self.view.addConstraint(NSLayoutConstraint(item: WwebView, attribute: .top, relatedBy: .equal, toItem: self.webContainerView, attribute: .top, multiplier: 1, constant: 0))
    self.view.addConstraint(NSLayoutConstraint(item: WwebView, attribute: .bottom, relatedBy: .equal, toItem: self.webContainerView, attribute: .bottom, multiplier: 1, constant: 0))


}

Focus sur cette fonction getJSCookiesString

 public func getJSCookiesString(for cookies: [HTTPCookie]) -> String? {

    var result = ""
    let dateFormatter = DateFormatter()
    dateFormatter.timeZone = TimeZone(abbreviation: "UTC")
    dateFormatter.dateFormat = "EEE, d MMM yyyy HH:mm:ss zzz"

    for cookie in cookies {
        if cookie.name == "yout_cookie_name_want_to_sync" {
            result += "document.cookie='\(cookie.name)=\(cookie.value); domain=\(cookie.domain); path=\(cookie.path); "
            if let date = cookie.expiresDate {
                result += "expires=\(dateFormatter.string(from: date)); "
            }
            if (cookie.isSecure) {
                result += "secure; "
            }
            result += "'; "
        }

    }

    return result
}

Voici une autre étape wkuserscript ne synchronise pas les cookies immédiatement, il y a beaucoup de mal à charger la première page avec le cookie, l'un est de recharger à nouveau la vue Web si elle met fin au processus mais je ne recommande pas de l'utiliser, ce n'est pas bon pour le point de vue de l'utilisateur , diable est que chaque fois que vous êtes prêt à charger des cookies de demande dans l'en-tête de la demande, n'oubliez pas d'ajouter la vérification de la version iOS. avant la demande de chargement, appelez cette fonction.

request?.addCookies()

j'ai écrit une extension pour URLRequest

extension URLRequest {

internal mutating func addCookies() {
    //"appCode=anAuY28ucmFrdXRlbi5yZXdhcmQuaW9zLXpOQlRTRmNiejNHSzR0S0xuMGFRb0NjbUg4Ql9JVWJH;rpga=kW69IPVSYZTo0JkZBicUnFxC1g5FtoHwdln59Z5RNXgJoMToSBW4xAMqtf0YDfto;rewardadid=D9F8CE68-CF18-4EE6-A076-CC951A4301F6;rewardheader=true"
    var cookiesStr: String = ""

    if IOSVersion.SYSTEM_VERSION_LESS_THAN(version: "11.0") {
        let mutableRequest = ((self as NSURLRequest).mutableCopy() as? NSMutableURLRequest)!
        if let yourCookie = "YOUR_HTTP_COOKIE_OBJECT" {
            // if have more than one cookies dont forget to add ";" at end
            cookiesStr += yourCookie.name + "=" + yourCookie.value + ";"

            mutableRequest.setValue(cookiesStr, forHTTPHeaderField: "Cookie")
            self = mutableRequest as URLRequest

        }
    }

  }
}

vous êtes maintenant prêt à tester iOS> 8

Shauket Sheikh
la source
2

Veuillez trouver la solution qui fonctionnera le plus pour vous dès sa sortie de la boîte. Fondamentalement , il est modifié et mis à jour pour Swift 4 @ user3589213 de réponse .

func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
    let headerKeys = navigationAction.request.allHTTPHeaderFields?.keys
    let hasCookies = headerKeys?.contains("Cookie") ?? false

    if hasCookies {
        decisionHandler(.allow)
    } else {
        let cookies = HTTPCookie.requestHeaderFields(with: HTTPCookieStorage.shared.cookies ?? [])

        var headers = navigationAction.request.allHTTPHeaderFields ?? [:]
        headers += cookies

        var req = navigationAction.request
        req.allHTTPHeaderFields = headers

        webView.load(req)

        decisionHandler(.cancel)
    }
}
Vadim Bulavin
la source
1

J'ai essayé toutes les réponses ci-dessus mais aucune d'elles ne fonctionne. Après tant de tentatives, j'ai enfin trouvé un moyen fiable de définir le cookie WKWebview.

Vous devez d'abord créer une instance de WKProcessPool et la définir sur WKWebViewConfiguration qui doit être utilisée pour initialiser le WkWebview lui-même:

    private lazy var mainWebView: WKWebView = {
        let webConfiguration = WKWebViewConfiguration()
        webConfiguration.processPool = WKProcessPool()
        let webView = WKWebView(frame: .zero, configuration: webConfiguration)
        webView.navigationDelegate = self
        return webView
    }()

La configuration de WKProcessPool est l'étape la plus importante ici. WKWebview utilise l'isolation de processus - ce qui signifie qu'il s'exécute sur un processus différent de celui de votre application. Cela peut parfois provoquer des conflits et empêcher votre cookie d'être correctement synchronisé avec WKWebview.

Regardons maintenant la définition de WKProcessPool

Le pool de processus associé à une vue Web est spécifié par sa configuration de vue Web. Chaque vue Web reçoit son propre processus de contenu Web jusqu'à ce qu'une limite de processus définie par l'implémentation soit atteinte; après cela, les vues Web avec le même pool de processus finissent par partager des processus de contenu Web.

Faites attention à la dernière phrase si vous prévoyez d'utiliser le même WKWebview pour les demandes de sous-séquences

les vues Web avec le même pool de processus finissent par partager des processus de contenu Web

ce que je veux dire, c'est que si vous n'utilisez pas la même instance de WKProcessPool à chaque fois que vous configurez un WKWebView pour le même domaine (peut-être que vous avez un VC A qui contient un WKWebView et que vous souhaitez créer différentes instances de VC A à différents endroits ), il peut y avoir des cookies de paramétrage de conflit. Pour résoudre le problème, après la première création du WKProcessPool pour un WKWebView qui charge le domaine B, je l'enregistre dans un singleton et utilise ce même WKProcessPool chaque fois que je dois créer un WKWebView qui charge le même domaine B

private lazy var mainWebView: WKWebView = {
    let webConfiguration = WKWebViewConfiguration()
    if Enviroment.shared.processPool == nil {
        Enviroment.shared.processPool = WKProcessPool()
    }
    webConfiguration.processPool = Enviroment.shared.processPool!
    webConfiguration.processPool = WKProcessPool()
    let webView = WKWebView(frame: .zero, configuration: webConfiguration)
    webView.navigationDelegate = self
    return webView
}()

Après le processus d'initialisation, vous pouvez charger une URLRequest dans le bloc de complétion de httpCookieStore.setCookie. Ici, vous devez attacher le cookie à l'en-tête de la demande sinon cela ne fonctionnera pas.

P / s: J'ai volé l'extension de la réponse fantastique ci-dessus de Dan Loewenherz

mainWebView.configuration.websiteDataStore.httpCookieStore.setCookie(your_cookie) {
        self.mainWebView.load(your_request, with: [your_cookie])
}

extension WKWebView {
   func load(_ request: URLRequest, with cookies: [HTTPCookie]) {
      var request = request
      let headers = HTTPCookie.requestHeaderFields(with: cookies)
      for (name, value) in headers {
         request.addValue(value, forHTTPHeaderField: name)
      }        
      load(request)
   }
}
Linh Ta
la source
1

Ma version de la réponse de nteiss. Testé sur iOS 11, 12, 13. On dirait que vous n'avez pas à utiliser DispatchGroupsur iOS 13plus.

J'utilise la fonction non statique includeCustomCookiessur WKWebViewConfiguration, afin que je puisse mettre à jourcookies chaque fois que je crée de nouveaux WKWebViewConfiguration.

extension WKWebViewConfiguration {
    func includeCustomCookies(cookies: [HTTPCookie], completion: @escaping  () -> Void) {
        let dataStore = WKWebsiteDataStore.nonPersistent()
        let waitGroup = DispatchGroup()

        for cookie in cookies {
            waitGroup.enter()
            dataStore.httpCookieStore.setCookie(cookie) { waitGroup.leave() }
        }

        waitGroup.notify(queue: DispatchQueue.main) {
            self.websiteDataStore = dataStore
            completion()
        }
    }
}

Ensuite, je l'utilise comme ceci:

let customUserAgent: String = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.1.1 Safari/605.1.15"

let customCookies: [HTTPCookie] = {
    let cookie1 = HTTPCookie(properties: [
        .domain: "yourdomain.com",
        .path: "/",
        .name: "auth_token",
        .value: APIManager.authToken
    ])!

    let cookie2 = HTTPCookie(properties: [
        .domain: "yourdomain.com",
        .path: "/",
        .name: "i18next",
        .value: "ru"
    ])!

    return [cookie1, cookie2]
}()

override func viewDidLoad() {
    super.viewDidLoad()

    activityIndicatorView.startAnimating()

    let webConfiguration = WKWebViewConfiguration()
    webConfiguration.includeCustomCookies(cookies: customCookies, completion: { [weak self] in
        guard let strongSelf = self else { return }
        strongSelf.webView = WKWebView(frame: strongSelf.view.bounds, configuration: webConfiguration)
        strongSelf.webView.customUserAgent = strongSelf.customUserAgent
        strongSelf.webView.navigationDelegate = strongSelf
        strongSelf.webView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        strongSelf.view.addSubview(strongSelf.webView)
        strongSelf.view.bringSubviewToFront(strongSelf.activityIndicatorView)
        strongSelf.webView.load(strongSelf.request)
    })
}
Denis Kutlubaev
la source
0

Le meilleur correctif pour les demandes XHR est montré ici

Version Swift 4:

func webView(_ webView: WKWebView, decidePolicyFor navigationResponse: WKNavigationResponse, decisionHandler: @escaping (WKNavigationResponsePolicy) -> Swift.Void) {
    guard
        let response = navigationResponse.response as? HTTPURLResponse,
        let url = navigationResponse.response.url
    else {
        decisionHandler(.cancel)
        return
    }

    if let headerFields = response.allHeaderFields as? [String: String] {
        let cookies = HTTPCookie.cookies(withResponseHeaderFields: headerFields, for: url)
        cookies.forEach { (cookie) in
            HTTPCookieStorage.shared.setCookie(cookie)
        }
    }

    decisionHandler(.allow)
}
Lloyd Keijzer
la source
0

Si quelqu'un utilise Alamofire, c'est la meilleure solution.

  let cookies = Alamofire.SessionManager.default.session.configuration.httpCookieStorage?.cookies(for: URL(string: BASE_URL)!)
  for (cookie) in cookies ?? [] {
      webView.configuration.websiteDataStore.httpCookieStore.setCookie(cookie)
  }
jeet.chanchawat
la source
0

Cela fonctionne pour moi: après avoir défini les cookies, ajoutez fetchdatarecords

   let cookiesSet = NetworkProvider.getCookies(forKey : 
    PaywallProvider.COOKIES_KEY, completionHandler: nil)
                let dispatchGroup = DispatchGroup()
                for (cookie) in cookiesSet {
                    if #available(iOS 11.0, *) {
                        dispatchGroup.enter()
                        self.webView.configuration.websiteDataStore.httpCookieStore.setCookie(cookie){
                            dispatchGroup.leave()
                            print ("cookie added: \(cookie.description)")
                            }
                        } else {
                                            // TODO Handle ios 10 Fallback on earlier versions
                        }
                    }
                    dispatchGroup.notify(queue: .main, execute: {


    self.webView.configuration.websiteDataStore.fetchDataRecords(ofTypes: 
    WKWebsiteDataStore.allWebsiteDataTypes()) { records in
                            records.forEach { record in

                                print("[WebCacheCleaner] Record \(record)")
                            }
                            self.webView.load(URLRequest(url: 
    self.dataController.premiumArticleURL , 
    cachePolicy:NSURLRequest.CachePolicy.reloadIgnoringLocalAndRemoteCacheData,
                                                         timeoutInterval: 10.0))
                        }

                    })
                }
adar tzeiri
la source
0

Lors de l'ajout d'éléments multi-cookies, vous pouvez le faire comme ceci: ( path& domainest requis pour chaque élément)

NSString *cookie = [NSString stringWithFormat:@"document.cookie = 'p1=%@;path=/;domain=your.domain;';document.cookie = 'p2=%@;path=/;domain=your.domain;';document.cookie = 'p3=%@;path=/;domain=your.domain;';", p1_string, p2_string, p3_string];

WKUserScript *cookieScript = [[WKUserScript alloc]
            initWithSource:cookie
            injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:NO];

[userContentController addUserScript:cookieScript];

sinon, seul le premier élément de cookie sera défini.

YanXing Ou
la source
0

Vous pouvez également utiliser WKWebsiteDataStore pour obtenir un comportement similaire à HTTPCookieStorage à partir d'UIWebView.

let dataStore = WKWebsiteDataStore.default()
let cookies = HTTPCookieStorage.shared.cookies ?? [HTTPCookie]()
cookies.forEach({
    dataStore.httpCookieStore.setCookie($0, completionHandler: nil)
})
Tigu
la source
0

Le code ci-dessous fonctionne bien dans mon projet Swift5. essayez de charger l'url par WKWebView ci-dessous:

    private func loadURL(urlString: String) {
        let url = URL(string: urlString)
        guard let urlToLoad = url else { fatalError("Cannot find any URL") }

        // Cookies configuration
        var urlRequest = URLRequest(url: urlToLoad)
        if let cookies = HTTPCookieStorage.shared.cookies(for: urlToLoad) {
            let headers = HTTPCookie.requestHeaderFields(with: cookies)
            for header in headers { urlRequest.addValue(header.value, forHTTPHeaderField: header.key) }
        }

        webview.load(urlRequest)
    }
Vansa Bean
la source
0

C'est ma solution pour gérer les cookies et WKWebView sous iOS 9 ou version ultérieure.

import WebKit

extension WebView {

    enum LayoutMode {
        case fillContainer
    }

    func autoLayout(_ view: UIView?, mode: WebView.LayoutMode = .fillContainer) {
        guard let view = view else { return }
        self.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(self)

        switch mode {
        case .fillContainer:
                NSLayoutConstraint.activate([
                self.topAnchor.constraint(equalTo: view.topAnchor),
                self.leadingAnchor.constraint(equalTo: view.leadingAnchor),
                self.trailingAnchor.constraint(equalTo: view.trailingAnchor),
                self.bottomAnchor.constraint(equalTo: view.bottomAnchor)
            ])
        }
    }

}

class WebView : WKWebView {

    var request : URLRequest?

    func load(url: URL, useSharedCookies: Bool = false) {
        if useSharedCookies, let cookies = HTTPCookieStorage.shared.cookies(for: url) {
            self.load(url: url, withCookies: cookies)
        } else {
            self.load(URLRequest(url: url))
        }
    }

    func load(url: URL, withCookies cookies: [HTTPCookie]) {
        self.request = URLRequest(url: url)
        let headers = HTTPCookie.requestHeaderFields(with: cookies)
        self.request?.allHTTPHeaderFields = headers
        self.load(request!)
    }

}
Giuseppe Mazzilli
la source
0

Cette erreur que je faisais est que je passais l'URL entière dans l'attribut de domaine, ce ne devrait être que le nom de domaine.

let cookie = HTTPCookie(properties: [
.domain: "example.com",
.path: "/",
.name: "MyCookieName",
.value: "MyCookieValue",
.secure: "TRUE",
])! 

webView.configuration.websiteDataStore.httpCookieStore.setCookie(cookie)
Muhammad Aamir Ali
la source