Posso impostare i cookie per essere utilizzati da un WKWebView?


135

Sto cercando di passare da un'app esistente UIWebViewa WKWebView. L'app corrente gestisce l'accesso / sessione degli utenti al di fuori di webviewe imposta i cookiesnecessari per l'autenticazione in NSHTTPCookieStore. Sfortunatamente il nuovo WKWebViewnon usa il cookiesda NSHTTPCookieStorage. C'è un altro modo per raggiungere questo obiettivo?

Risposte:


186

Modifica solo per iOS 11+

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

Dal momento che li stai trasferendo da HTTPCookeStorage, puoi farlo:

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

Vecchia risposta per iOS 10 e precedenti

Se è necessario impostare i cookie sulla richiesta di caricamento iniziale, è possibile impostarli su NSMutableURLRequest. Poiché i cookie sono solo un'intestazione di richiesta appositamente formattata, ciò può essere ottenuto in questo modo:

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

Se hai bisogno delle successive richieste AJAX sulla pagina per impostare i loro cookie, questo può essere ottenuto semplicemente usando WKUserScript per impostare i valori programmaticamente via javascript all'avvio del documento in questo modo:

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 combinazione di queste due tecniche dovrebbe fornire strumenti sufficienti per trasferire i valori dei cookie da Native App Land a Web View Land. Puoi trovare maggiori informazioni sull'API javascript dei cookie sulla pagina di Mozilla se hai bisogno di cookie più avanzati.

Sì, fa schifo che Apple non supporti molte delle bellezze di UIWebView . Non sono sicuro che li sosterranno mai, ma speriamo che lo faranno presto. Spero che questo ti aiuti!


1
Qual è il posto migliore per iniettare i cookie per le richieste successive? Ad esempio, il caricamento iniziale della pagina è trattato nella risposta sopra, ma cosa succede se nella pagina ci sono collegamenti che portano anche allo stesso dominio e necessitano anche degli stessi cookie iniettati nella richiesta? didStartProvisionalNavigation?
Mason G. Zhwiti,

1
scusa non funziona per te. Nella mia mente, fintanto che i domini sono uguali, non dovrebbero esserci problemi. Puoi ricontrollare nel codice che il link punta allo stesso dominio da cui hai caricato la richiesta? Inoltre, i cookie possono essere vincolati anche a un "percorso" specifico. Forse questo sta causando alcuni problemi?
mattr

11
Si noti che la tecnica javascript per impostare i cookie non funzionerà con i cookie "Solo HTTP".
Ahmed Nasser,

1
Il metodo sopra funziona alla grande ... ma ho potuto vedere i cookie duplicati nelle successive chiamate AJAX (duplicati solo una volta).
Durga Vundavalli,

1
@ Axel92Dev una soluzione alternativa sarebbe quella di assicurarsi che la prima richiesta fatta dalla tua visualizzazione Web al tuo server ottenga una risposta che dice esplicitamente alla visualizzazione Web di reimpostare i cookie con il flag HTTPOnly (ovvero: reimpostare i cookie nella risposta). È possibile creare un'API speciale per tale unico scopo durante l'inizializzazione della visualizzazione Web, quindi utilizzare normalmente la visualizzazione Web in caso di successo.
Ahmed Nasser,

64

Dopo aver giocato con questa risposta (che è stata straordinariamente utile :) abbiamo dovuto apportare alcune modifiche:

  • Abbiamo bisogno di visualizzazioni Web per gestire più domini senza perdere informazioni sui cookie privati ​​tra tali domini
  • Ne abbiamo bisogno per onorare i cookie sicuri
  • Se il server modifica un valore di cookie, desideriamo che la nostra app ne sia a conoscenza NSHTTPCookieStorage
  • Se il server modifica un valore di cookie, non vogliamo che i nostri script lo ripristinino al suo valore originale quando segui un collegamento / AJAX ecc.

Quindi abbiamo modificato il nostro codice per essere questo;

Creare una richiesta

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...

Questo assicura che la prima richiesta abbia i cookie corretti impostati, senza inviare cookie dalla memoria condivisa che sono per altri domini e senza inviare cookie sicuri in una richiesta non sicura.

Gestire ulteriori richieste

Dobbiamo anche assicurarci che le altre richieste abbiano i cookie impostati. Questo viene fatto usando uno script che viene eseguito al caricamento del documento che verifica se esiste un cookie impostato e, in caso contrario, impostalo sul valore in 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];

Gestire le modifiche ai cookie

Dobbiamo anche occuparci del server che modifica il valore di un cookie. Ciò significa aggiungere un altro script per richiamare la visualizzazione Web che stiamo creando per aggiornare il nostro 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"];

e implementando il metodo delegato per aggiornare tutti i cookie che sono cambiati, assicurandoci di aggiornare solo i cookie dal dominio corrente!

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

Questo sembra risolvere i nostri problemi con i cookie senza che dobbiamo occuparci di ogni luogo in cui utilizziamo WKWebView in modo diverso. Ora possiamo semplicemente usare questo codice come aiuto per creare le nostre visualizzazioni Web e si aggiorna in modo trasparente NSHTTPCookieStorageper noi.


EDIT: Ho scoperto che ho usato una categoria privata su NSHTTPCookie - ecco il codice:

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

6
Ho inserito il tuo codice in una sottoclasse di WKWebView. Sentiti libero di dare un'occhiata github.com/haifengkao/YWebView
Hai Feng Kao

Cosa succede se i tuoi cookie contengono = segni nel valore? Funzionerebbe?
iOS Aggiunto il

@iOSAddicted Penso di sì. Se il tuo valore fosse a=b, finiresti con la stringa di cookie name=a=b;domain=.example.com;path=/- credo che lo standard si divida ;e poi si divida sul primo = nella coppia chiave = valore. Lo
testerei

la tua risposta mi ha aiutato molto, vorrei tuttavia aggiungere qualcosa al tuo post, ci sono diversi rischi quando si utilizza il metodo di aggiornamento, alcuni framework JS potrebbero creare cookie con lo stesso nome ma dominio diverso e se si tenta di aggiornarlo utilizzando i metodi js hai un alto rischio di aggiornare un cookie con un valore errato. Anche per noi la stringa di cookie js, ha dovuto essere spogliata della sua bandiera sicura, poiché il nostro server effettua cattivi reindirizzamenti tra http e https, facendo sì che i cookie sicuri non siano presenti in alcune pagine in alcuni cattivi casi limite.
Ricardo Duarte,

In realtà penso che la compagnia con cui stavo lavorando quando ho scritto questo ha dovuto aggiungere un po 'di protezione del dominio dopo che è diventata attiva. Non abbiamo mai (afaik) incontrato il problema sicuro / insicuro - sembra un incubo!
deanWombourne,

42

I cookie devono essere impostati sulla configurazione prima della WKWebViewcreazione. In caso contrario, anche con WKHTTPCookieStoreil setCookiegestore del completamento, i cookie non verranno sincronizzati in modo affidabile con la visualizzazione Web. Questo risale a questa riga dai documenti in poiWKWebViewConfiguration

@NSCopying var configuration: WKWebViewConfiguration { get }

È in @NSCopyingqualche modo una copia profonda. L'implementazione è al di là di me, ma il risultato finale è che se non imposti i cookie prima di inizializzare la visualizzazione Web, non puoi contare sul fatto che i cookie siano presenti. Ciò può complicare l'architettura dell'app perché l'inizializzazione di una vista diventa un processo asincrono. Finirai con qualcosa del genere

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

e poi usarlo qualcosa di simile

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'esempio sopra mostra la creazione della vista fino all'ultimo momento possibile, un'altra soluzione sarebbe quella di creare la configurazione o la vista web con largo anticipo e gestire la natura asincrona prima della creazione di un controller di vista.

Un'ultima nota: una volta creata questa visualizzazione Web, è stata rilasciata in libertà, non è possibile aggiungere più cookie senza utilizzare i metodi descritti in questa risposta . Puoi comunque utilizzare l' WKHTTPCookieStoreObserverAPI per osservare almeno le modifiche in atto sui cookie. Pertanto, se un cookie di sessione viene aggiornato nella visualizzazione Web, è possibile aggiornare manualmente il sistema HTTPCookieStoragecon questo nuovo cookie, se lo si desidera.

Per ulteriori informazioni, vai alle 18:00 in questo caricamento del contenuto Web personalizzato della sessione WWDC 2017 . All'inizio di questa sessione, c'è un esempio di codice ingannevole che omette il fatto che la visualizzazione Web debba essere creata nel gestore di completamento.

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

La demo live alle 18:00 chiarisce questo.

Modifica A partire da Mojave Beta 7 e iOS 12 Beta 7 almeno, vedo un comportamento molto più coerente con i cookie. Il setCookie(_:)metodo sembra persino consentire l'impostazione dei cookie dopo la WKWebViewcreazione. Ho trovato comunque importante non toccare affatto la processPoolvariabile. La funzionalità di impostazione dei cookie funziona meglio quando non vengono creati pool aggiuntivi e quando tale proprietà viene lasciata da sola. Penso che sia sicuro dire che stavamo riscontrando problemi a causa di alcuni bug in WebKit.


Sembra che la gestione / impostazione dei cookie sia più affidabile in Mojave 10.14 beta 3 e iOS 12 beta 3
nteissler

6
Risposta molto approfondita e sottovalutata
Nicolás Carrasco,

1
Ho ancora questo problema in iOS 12 con un WKWebView già caricato. A volte setCookie () verrà effettivamente sincronizzato con WKWebView subito, a volte non renderà la gestione un po 'sporadica
bmjohns

Ho ancora riscontrato problemi anche da quando il radar è stato affermato risolto, ma molto meno frequentemente. Con quale frequenza riscontri il fallimento dei cookie? Se hai un progetto riproducibile, abbastanza piccolo, ti consiglio di inviare un bug di webkit qui: webkit.org/reporting-bugs Puoi anche twittare Brady Eidson (bene) un architetto di webkit su apple che è molto sensibile a questo tipo di segnalazioni e bug.
nteissler,

questa è la risposta corretta - non è necessario tradurre manualmente i cookie come campi di intestazione in ogni URLRequest, è solo che setCookie () deve essere utilizzato come descritto qui.
Guillaume Laurent,

25

lavora per me

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

Straordinario hack, in particolare quanto iOS sia intransigente sulla sostituzione di un cookie in un WKWebView esistente. L'unico problema è che il precedente WKNavigationKey è diventato stantio. Altro codice potrebbe essere in attesa invano su quello vecchio.
BaseZen

2
è corretto? Apprezzo che possa funzionare in alcune circostanze. Tuttavia la responsabilità di questo metodo delegato - decidePolicyForNavigationAction - è di decidere la politica; non caricare effettivamente la richiesta. Questo è stato avviato in precedenza. In tal caso, ciò non causa il caricamento della richiesta due volte?
Max MacLeod,

2
@MaxMacLeod Nella elsecondizione in cui sta chiamando la decisionHandlerchiusura in .cancelmodo webviewche non carichi effettivamente la richiesta iniziale. Dopo che loadRequestviene chiamato nella elsecondizione, questo metodo delegato verrà nuovamente chiamato per quella richiesta e passerà alla ifcondizione perché Cookiesarà presente l' intestazione.
halil_g

2
Anche se questo non funzionerebbe quando nella richiesta iniziale sono già impostati alcuni cookie, poiché non entrerà mai nella elsecondizione.
halil_g

Si noti che questo è 1) Non funziona per ogni situazione, ad esempio nel caso in cui la visualizzazione Web carichi i frame 2) Non sicuro: potrebbe inviare cookie con informazioni riservate all'URL di terze parti
Peter Prokop,

20

Ecco la mia versione della soluzione Mattrs in Swift per l'iniezione di tutti i cookie da HTTPCookieStorage. Ciò è stato fatto principalmente per iniettare un cookie di autenticazione per creare una sessione utente.

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
}

meglio aggiungere questa riga per assicurarsi che la formattazione delle dateFormatter.locale = NSLocale(localeIdentifier: "en_US_POSIX")
impostazioni

dove chiamarlo?
markhorrocks,

Questo ha funzionato bene per me in Swift 4 (con piccoli aggiustamenti)
Frédéric Adda

Questo funziona alla grande per me, ma solo la seconda volta che visito il sito (la prima volta che i cookie non sono impostati) - qualcuno si imbatte in questo?
MrChrisBarker,

Primo caricamento dare errore secondo caricamento lavoro :( quale potrebbe essere il problema?
Shauket Sheikh

10

imposta cookie

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

elimina cookie

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

9

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

1
Ciao, puoi aggiungere codice per ottenere anche i cookie HTTPCookieStorage.shared?
Markhorrocks

Questo è l'unico modo in cui ho ottenuto WKWebView per aggiungere i cookie a ogni richiesta fatta dalla visualizzazione web
Chicowitz,

Se contiene un cookie httponly in risposta, non è possibile ottenere il valore dei cookie in questo modo.
cervello,

1
questo sta solo riportando i cookie nella memoria di httpcookies dove si trova il codice che imposta i cookie per wkwebview?
Shauket Sheikh,

8

Dopo aver esaminato varie risposte qui e non avendo avuto successo, ho analizzato la documentazione di WebKit e mi sono imbattuto nel requestHeaderFieldsmetodo statico HTTPCookie, che converte una matrice di cookie in un formato adatto per un campo di intestazione. Combinando questo con l'intuizione di mattr di aggiornare il URLRequestprima di caricarlo con le intestazioni dei cookie mi ha portato al traguardo.

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)

Per rendere questo ancora più semplice, usa un'estensione:

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

Ora diventa solo:

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

Questa estensione è disponibile anche in LionheartExtensions se desideri semplicemente una soluzione drop-in. Saluti!


1
@ShauketSheikh hmm, in quali situazioni non funziona?
Dan Loewenherz,

ho testato usando il simulatore iOS 8 sembra non inviare cookie. l'ho verificato due volte.
Shauket Sheikh,

ho pubblicato la mia risposta, puoi provare @Dan
Shauket Sheikh il

7

In iOS 11, puoi gestire i cookie ora :), vedi questa sessione: https://developer.apple.com/videos/play/wwdc2017/220/

inserisci qui la descrizione dell'immagine


2
@ShobhakarTiwari perché? si verificano cambiamenti nella versione formale di iOS11?
Jacky,

Il modo migliore per procedere se supporti solo iOS 11 e versioni successive, se devi supportare versioni precedenti, utilizza JavaScript prima del caricamento della pagina.
PashaN,

Questo funziona per me, tranne per il fatto che a volte il metodo setcookie NON esegue il suo gestore di completamento, il che significa che a volte la mia pagina web non viene caricata - accade solo sul dispositivo, accade la 3a / 4a / 5a volta chiusura e riapertura la webview, e dopo che succede una volta, continua a succedere fino a quando non ripristino l'app - qualcuno si imbatte anche in questo?
Binya Koatz,

5

Il motivo alla base della pubblicazione di questa risposta è che ho provato molte soluzioni, ma nessuno funziona correttamente, la maggior parte della risposta non funziona nel caso in cui sia necessario impostare i cookie per la prima volta e ottenere i cookie dei risultati non sincronizzati la prima volta, utilizzare questa soluzione funziona per entrambi iOS> = 11.0 <= iOS 11 fino 8.0, funziona anche con la sincronizzazione dei cookie per la prima volta.

Per iOS> = 11.0 - Swift 4.2

Ricevi i cookie http e impostali nel negozio di cookie di wkwebview in questo modo, è molto complicato caricare la tua richiesta in wkwebview , devi inviare una richiesta di caricamento quando i cookie verranno impostati completamente, ecco la funzione che ho scritto.

Funzione di chiamata con chiusura in corso si chiama load webview. Cordiali saluti, questa funzione gestisce solo iOS> = 11.0

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

Ecco l'implementazione per la funzione 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()
}
}

Per iOS 8 fino a iOS 11

devi impostare alcune cose extra che devi impostare due volte i cookie uno tramite WKUserScript e non dimenticare di aggiungere anche i cookie su richiesta, altrimenti il ​​tuo cookie non si sincronizzerà la prima volta e vedrai che la pagina non verrà caricata correttamente la prima volta. questo è il diavolo che ho trovato per supportare i cookie per iOS 8.0

prima di creare l'oggetto 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))


}

Concentrati su questa funzione 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
}

Ecco un altro passo che wkuserscript non sincronizza immediatamente i cookie, c'è molto da fare per caricare la prima volta la pagina con il cookie uno è ricaricare nuovamente la visualizzazione web se termina il processo ma non consiglio di usarlo, non è buono per il punto di vista dell'utente , diamine è ogni volta che sei pronto a caricare i cookie di richiesta impostata nell'intestazione della richiesta e in questo modo, non dimenticare di aggiungere il controllo della versione di iOS. prima di caricare la richiesta chiamare questa funzione.

request?.addCookies()

ho scritto l'estensione per 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

        }
    }

  }
}

ora sei pronto per provare iOS> 8


2

Trova immediatamente la soluzione che molto probabilmente funzionerà per te. Fondamentalmente viene modificato e aggiornato per la risposta di Swift 4 @ user3589213 .

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

1

Ho provato tutte le risposte sopra ma nessuna di esse funziona. Dopo tanti tentativi ho finalmente trovato un modo affidabile per impostare i cookie di WKWebview.

Innanzitutto devi creare un'istanza di WKProcessPool e impostarla su WKWebViewConfiguration che deve essere utilizzata per inizializzare WkWebview stesso:

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

L'impostazione di WKProcessPool è il passaggio più importante qui. WKWebview utilizza l'isolamento del processo, il che significa che viene eseguito su un processo diverso rispetto al processo della tua app. Questo a volte può causare conflitti e impedire la corretta sincronizzazione dei cookie con WKWebview.

Ora diamo un'occhiata alla definizione di WKProcessPool

Il pool di processi associato a una vista Web è specificato dalla sua configurazione della vista Web. A ciascuna vista Web viene assegnato il proprio processo di contenuto Web fino al raggiungimento di un limite di processo definito dall'implementazione; successivamente, le visualizzazioni Web con lo stesso pool di processi finiscono per condividere i processi di contenuto Web.

Prestare attenzione all'ultima frase se si prevede di utilizzare lo stesso WKWebview per le richieste di sottosequenza

le visualizzazioni Web con lo stesso pool di processi finiscono per condividere i processi di contenuto Web

ciò che intendo è che se non si utilizza la stessa istanza di WKProcessPool ogni volta che si configura un WKWebView per lo stesso dominio (forse si dispone di un VC A che contiene un WKWebView e si desidera creare istanze diverse di VC A in luoghi diversi ), potrebbero esserci dei cookie per l'impostazione dei conflitti. Per risolvere il problema, dopo la prima creazione di WKProcessPool per un WKWebView che carica il dominio B, lo salvo in un singleton e utilizzo lo stesso WKProcessPool ogni volta che devo creare un WKWebView che carica lo stesso dominio 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
}()

Dopo il processo di inizializzazione, è possibile caricare un URLRequest all'interno del blocco di completamento di httpCookieStore.setCookie. Qui, devi allegare il cookie all'intestazione della richiesta, altrimenti non funzionerà.

P / s: ho rubato l'estensione dalla fantastica risposta sopra di 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)
   }
}

1

La mia versione della risposta di nteiss. Testato su iOS 11, 12, 13. Sembra che non c'è bisogno di utilizzare DispatchGroupsu iOS 13più.

Uso la funzione non statica includeCustomCookiessu WKWebViewConfiguration, in modo da poter aggiornare cookiesogni volta che ne creo di nuovi 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()
        }
    }
}

Quindi lo uso così:

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

0

La soluzione migliore per le richieste XHR è mostrata qui

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

0

Se qualcuno sta usando Alamofire, questa è la soluzione migliore.

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

0

Questo funziona per me: dopo setcookies, aggiungi 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))
                        }

                    })
                }

0

Quando aggiungi molti elementi cookie, puoi farlo in questo modo: ( path& domainè richiesto per ogni elemento)

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

in caso contrario, verrà impostato solo il primo elemento cookie.


0

Puoi anche utilizzare WKWebsiteDataStore per ottenere un comportamento simile a HTTPCookieStorage da UIWebView.

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

0

Di seguito il codice funziona bene nel mio progetto Swift5. prova a caricare l'URL di WKWebView di seguito:

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

0

Questa è la mia soluzione da gestire con i cookie e WKWebView in iOS 9 o versioni successive.

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

}

0

Questo errore che stavo facendo è stato passare l'intero URL nell'attributo di dominio, dovrebbe essere solo il nome di dominio.

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

webView.configuration.websiteDataStore.httpCookieStore.setCookie(cookie)
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.