Kann ich die Cookies so einstellen, dass sie von einem WKWebView verwendet werden?

134

Ich versuche, eine vorhandene App von UIWebViewauf umzustellen WKWebView. Die aktuelle App verwaltet die Benutzeranmeldung / -sitzung außerhalb der webviewund legt die cookiesfür die Authentifizierung erforderliche in der fest NSHTTPCookieStore. Leider WKWebViewnutzt new das nicht cookiesaus dem NSHTTPCookieStorage. Gibt es einen anderen Weg, um dies zu erreichen?

Col.
quelle

Antworten:

185

Nur für iOS 11+ bearbeiten

Verwenden Sie 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)

Da Sie sie von HTTPCookeStorage abrufen, können Sie Folgendes tun:

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

Alte Antwort für iOS 10 und niedriger

Wenn Sie möchten, dass Ihre Cookies bei der ersten Ladeanforderung gesetzt werden, können Sie sie bei NSMutableURLRequest setzen. Da Cookies nur ein speziell formatierter Anforderungsheader sind, kann dies folgendermaßen erreicht werden:

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

Wenn nachfolgende AJAX-Anforderungen auf der Seite ihre Cookies setzen müssen, können Sie dies einfach mit WKUserScript erreichen, um die Werte beim Start des Dokuments programmgesteuert über Javascript wie folgt festzulegen:

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

Durch die Kombination dieser beiden Techniken sollten Sie über genügend Tools verfügen, um Cookie-Werte von Native App Land in Web View Land zu übertragen. Weitere Informationen zur Cookie-Javascript-API finden Sie auf der Mozilla-Seite, wenn Sie erweiterte Cookies benötigen.

Ja, es ist schade , dass Apple viele der Feinheiten von UIWebView nicht unterstützt . Ich bin mir nicht sicher, ob sie sie jemals unterstützen werden, aber hoffentlich werden sie es bald schaffen. Hoffe das hilft!

mattr
quelle
1
Wo kann man Cookies am besten für spätere Anfragen einfügen? Beispiel: Das anfängliche Laden der Seite wird in der obigen Antwort behandelt. Was ist jedoch, wenn auf der Seite Links vorhanden sind, die ebenfalls zu derselben Domain führen und dieselben Cookies in die Anfrage einfügen müssen? didStartProvisionalNavigation?
Mason G. Zhwiti
1
Entschuldigung, es funktioniert nicht für Sie. Meiner Meinung nach sollte es kein Problem geben, solange die Domains gleich sind. Können Sie den Code überprüfen, dass der Link auf dieselbe Domain verweist, von der Sie die Anfrage geladen haben? Cookies können auch auf einen bestimmten "Pfad" beschränkt werden. Vielleicht verursacht das einige Probleme?
Mattr
11
Beachten Sie, dass die Javascript-Technik zum Setzen der Cookies für "Nur HTTP" -Cookies nicht funktioniert.
Ahmed Nasser
1
Die obige Methode funktioniert hervorragend ... aber ich konnte sehen, dass Cookies in den nachfolgenden AJAX-Aufrufen dupliziert wurden (nur einmal dupliziert).
Durga Vundavalli
1
@ Axel92Dev Eine Problemumgehung besteht darin, sicherzustellen, dass die erste Anforderung, die von Ihrer Webansicht an Ihren Server gesendet wird, eine Antwort erhält, die die Webansicht explizit anweist, die Cookies erneut mit dem HTTPOnly-Flag zu setzen (dh: Setzen Sie die Cookies in der Antwort erneut). Sie können eine spezielle API für diesen alleinigen Zweck erstellen, wenn Sie die Webansicht initialisieren, und dann die Webansicht normalerweise bei Erfolg verwenden.
Ahmed Nasser
63

Nachdem wir mit dieser Antwort gespielt hatten (was fantastisch hilfreich war :), mussten wir einige Änderungen vornehmen:

  • Wir benötigen Webansichten, um mit mehreren Domains umgehen zu können, ohne dass private Cookie-Informationen zwischen diesen Domains verloren gehen
  • Wir brauchen es, um sichere Cookies zu ehren
  • Wenn der Server einen Cookie-Wert ändert, soll unsere App darüber informiert werden NSHTTPCookieStorage
  • Wenn der Server einen Cookie-Wert ändert, möchten wir nicht, dass unsere Skripte ihn auf seinen ursprünglichen Wert zurücksetzen, wenn Sie einem Link / AJAX usw. folgen.

Also haben wir unseren Code so geändert;

Anfrage erstellen

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

Dadurch wird sichergestellt, dass für die erste Anforderung die richtigen Cookies gesetzt sind, ohne dass Cookies aus dem gemeinsam genutzten Speicher für andere Domänen gesendet werden und ohne dass sichere Cookies an eine unsichere Anforderung gesendet werden.

Bearbeitung weiterer Anfragen

Wir müssen auch sicherstellen, dass für andere Anfragen die Cookies gesetzt sind. Dies erfolgt mithilfe eines Skripts, das beim Laden des Dokuments ausgeführt wird und prüft, ob ein Cookie-Set vorhanden ist. Wenn nicht, setzen Sie es auf den Wert 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];

Umgang mit Cookie-Änderungen

Wir müssen uns auch damit befassen, dass der Server den Wert eines Cookies ändert. Dies bedeutet, dass Sie ein weiteres Skript hinzufügen, um aus der von uns erstellten Webansicht zurückzurufen und unsere zu aktualisieren 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"];

und Implementieren der Delegate-Methode zum Aktualisieren geänderter Cookies, um sicherzustellen, dass nur Cookies von der aktuellen Domain aktualisiert werden!

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

Dies scheint unsere Cookie-Probleme zu beheben, ohne dass wir mit jedem Ort, an dem wir WKWebView verwenden, anders umgehen müssen. Wir können diesen Code jetzt nur als Hilfsmittel zum Erstellen unserer Webansichten verwenden und er wird NSHTTPCookieStoragefür uns transparent aktualisiert .


BEARBEITEN: Es stellte sich heraus, dass ich eine private Kategorie für NSHTTPCookie verwendet habe - hier ist der 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
quelle
6
Ich habe Ihren Code in eine Unterklasse von WKWebView eingewickelt. Fühlen Sie sich frei, es zu überprüfen github.com/haifengkao/YWebView
Hai Feng Kao
Was ist, wenn Ihre Cookies = Zeichen im Wert enthalten? Würde das funktionieren?
iOSAddicted
@iOSAddicted denke ich schon. Wenn Ihr Wert a=bwäre, würden Sie am Ende die Cookie-Zeichenfolge erhalten name=a=b;domain=.example.com;path=/- ich glaube, dass sich der Standard auf ;und dann auf den ersten = im Schlüssel = Wert-Paar aufteilt. Ich würde dies allerdings testen :)
DeanWombourne
Ihre Antwort hat mir sehr geholfen. Ich möchte jedoch etwas zu Ihrem Beitrag hinzufügen. Es gibt verschiedene Risiken bei der Verwendung Ihrer Aktualisierungsmethode. Einige JS-Frameworks erstellen möglicherweise Cookies mit demselben Namen, aber unterschiedlicher Domain. Wenn Sie versuchen, diese zu aktualisieren Mit den js-Methoden besteht ein hohes Risiko, dass ein Cookie mit einem falschen Wert aktualisiert wird. Auch für uns musste die js-Cookie-Zeichenfolge von ihrem sicheren Flag befreit werden, da unser Server böse Weiterleitungen zwischen http und https durchführt, was dazu führt, dass sichere Cookies auf einigen Seiten in einigen bösen Randfällen nicht vorhanden sind.
RicardoDuarte
Ich denke tatsächlich, dass die Firma, mit der ich zusammen war, als ich dies schrieb, nach dem Start einen gewissen Domain-Schutz hinzufügen musste. Wir sind nie (afaik) auf das sichere / unsichere Problem gestoßen - klingt wie ein Albtraum!
DeanWombourne
42

Die Cookies müssen in der Konfiguration gesetzt werden, bevor das WKWebViewerstellt wird. Andernfalls werden die Cookies selbst mit WKHTTPCookieStoredem setCookieCompletion-Handler nicht zuverlässig mit der Webansicht synchronisiert. Dies geht ab den Dokumenten auf diese Zeile zurückWKWebViewConfiguration

@NSCopying var configuration: WKWebViewConfiguration { get }

Das @NSCopyingist eine tiefe Kopie. Die Implementierung ist mir ein Rätsel, aber das Endergebnis ist, dass Sie nicht darauf zählen können, dass die Cookies vorhanden sind, wenn Sie vor dem Initialisieren der Webansicht keine Cookies setzen. Dies kann die App-Architektur komplizieren, da das Initialisieren einer Ansicht zu einem asynchronen Prozess wird. Sie werden mit so etwas enden

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

und dann so etwas zu benutzen

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

Das obige Beispiel verschiebt die Erstellung von Ansichten bis zum letztmöglichen Moment. Eine andere Lösung besteht darin, die Konfiguration oder Webansicht frühzeitig zu erstellen und die asynchrone Natur vor der Erstellung eines Ansichtscontrollers zu behandeln.

Ein letzter Hinweis: Sobald Sie diese Webansicht erstellt haben, können Sie keine weiteren Cookies mehr hinzufügen, ohne die in dieser Antwort beschriebenen Methoden zu verwenden . Sie können die WKHTTPCookieStoreObserverAPI jedoch verwenden , um zumindest Änderungen an Cookies zu beobachten. Wenn also ein Sitzungscookie in der Webansicht aktualisiert wird, können Sie das System HTTPCookieStoragebei Bedarf manuell mit diesem neuen Cookie aktualisieren .

Weitere Informationen hierzu finden Sie in dieser WWDC-Sitzung 2017 zum Laden von benutzerdefinierten Webinhalten bis 18:00 Uhr . Zu Beginn dieser Sitzung gibt es ein irreführendes Codebeispiel, bei dem die Tatsache weggelassen wird, dass die Webansicht im Abschlusshandler erstellt werden sollte.

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

Die Live-Demo um 18:00 Uhr verdeutlicht dies.

Bearbeiten Ab Mojave Beta 7 und iOS 12 Beta 7 sehe ich ein viel konsistenteres Verhalten mit Cookies. Die setCookie(_:)Methode scheint sogar das Setzen von Cookies zu ermöglichen, nachdem das WKWebViewerstellt wurde. Ich fand es jedoch wichtig, die Variable überhaupt nicht zu berührenprocessPool . Die Cookie-Einstellungsfunktion funktioniert am besten, wenn keine zusätzlichen Pools erstellt werden und diese Eigenschaft gut in Ruhe gelassen wird. Ich denke, man kann mit Sicherheit sagen, dass wir aufgrund einiger Fehler in WebKit Probleme hatten.

nteissler
quelle
Es sieht so aus, als ob die Handhabung / Einstellung von Cookies in Mojave 10.14 Beta 3 und iOS 12 Beta 3
Nteissler
6
Sehr ausführliche und unterschätzte Antwort
Nicolás Carrasco
1
Ich habe immer noch dieses Problem in iOS 12 mit einem bereits geladenen WKWebView. Manchmal wird setCookie () tatsächlich sofort mit dem WKWebView synchronisiert, manchmal wird das Handling dadurch nicht etwas sporadisch
bmjohns
Ich habe immer noch Probleme gesehen, seit das Radar repariert wurde, aber viel seltener. Wie oft sehen Sie den Cookie-Fehler? Wenn Sie ein reproduzierbares Projekt haben, das klein genug ist, würde ich wirklich empfehlen, hier einen Webkit-Fehler einzureichen: webkit.org/reporting-bugs Sie können auch Brady Eidson (nett), einen Webkit-Architekten bei Apple, twittern, der sehr auf diese Art von reagiert Berichte und Fehler.
Nteissler
Dies ist die richtige Antwort - Sie müssen die Cookies nicht manuell als Header-Felder in jeder URLRequest übersetzen, sondern nur setCookie () wie hier beschrieben verwenden.
Guillaume Laurent
25

arbeite für mich

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
quelle
Genialer Hack, besonders wie unnachgiebig iOS ist, ein Cookie in einem vorhandenen WKWebView zu überschreiben. Das einzige Problem ist, dass der vorherige WKNavigationKey veraltet ist. Ein anderer Code wartet möglicherweise vergeblich auf den alten.
BaseZen
2
ist das richtig? Schätzen Sie, dass es unter bestimmten Umständen funktionieren kann. Die Verantwortung dieser Delegatenmethode - entscheidendPolicyForNavigationAction - liegt jedoch in der Entscheidung über die Richtlinie. die Anfrage nicht wirklich zu laden. Das wurde schon früher eingeleitet. In diesem Fall wird die Anforderung dadurch nicht zweimal geladen?
Max MacLeod
2
@MaxMacLeod In der elseer ruft den Zustand decisionHandlerSchließung mit .cancelso das webvieweigentlich nicht die ursprüngliche Anforderung laden. Nachdem das loadRequestin der elseBedingung aufgerufen wurde , wird diese Delegatenmethode für diese Anforderung erneut aufgerufen und geht in die ifBedingung über, da der CookieHeader vorhanden ist.
halil_g
2
Dies würde jedoch nicht funktionieren, wenn für die erste Anforderung bereits einige Cookies gesetzt sind, da diese niemals in den elseZustand versetzt werden.
halil_g
Beachten Sie, dass dies 1) nicht für jede Situation funktioniert - zum Beispiel, wenn die Webansicht Frames lädt 2) nicht sicher - es könnte ein Cookie mit vertraulichen Informationen an die URL eines Drittanbieters senden
Peter Prokop
20

Hier ist meine Version der Mattrs- Lösung in Swift zum Injizieren aller Cookies von HTTPCookieStorage. Dies wurde hauptsächlich durchgeführt, um ein Authentifizierungscookie zum Erstellen einer Benutzersitzung einzufügen.

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
}
Mischa
quelle
dateFormatter.locale = NSLocale(localeIdentifier: "en_US_POSIX")
Fügen
wo soll man das nennen?
Markhorrocks
Dies funktionierte gut für mich in Swift 4 (mit geringfügigen Anpassungen)
Frédéric Adda
Das funktioniert gut für mich, aber erst beim zweiten Besuch der Website (wenn die Cookies zum ersten Mal nicht gesetzt werden) - stößt jemand darauf?
MrChrisBarker
Erste Ladung geben Fehler zweite Last Arbeit :( Was könnte das Problem sein?
Shauket Sheikh
10

Cookie setzen

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

Cookie löschen

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

Swift 3 Update:

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)
    }
}
Deep Parekh
quelle
1
Hallo, können Sie auch Code hinzufügen, um Cookies zu erhalten HTTPCookieStorage.shared?
Markhorrocks
Dies ist die einzige Möglichkeit, WKWebView dazu zu bringen, Cookies zu jeder Anfrage hinzuzufügen, die von der Webansicht gestellt wird
Chicowitz
Wenn es als Antwort ein httponly-Cookie enthält, können Sie den Cookie-Wert auf diese Weise nicht erhalten.
Gehirn
1
Dies setzt nur das Zurücksetzen von Cookies auf den httpcookies-Speicher. Wo ist der Code, der Cookies für wkwebview einrichtet?
Shauket Sheikh
8

Nachdem ich hier verschiedene Antworten durchgesehen hatte und keinen Erfolg hatte, durchsuchte ich die WebKit-Dokumentation und stieß auf die requestHeaderFieldsstatische Methode on HTTPCookie, mit der ein Array von Cookies in ein für ein Headerfeld geeignetes Format konvertiert wird. Die Kombination mit Mattrs Einsicht, das URLRequestvor dem Laden mit den Cookie-Headern zu aktualisieren, brachte mich durch die Ziellinie.

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)

Verwenden Sie eine Erweiterung, um dies noch einfacher zu machen:

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

Jetzt wird es einfach:

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

Diese Erweiterung ist auch in LionheartExtensions verfügbar, wenn Sie nur eine Drop-In-Lösung wünschen. Prost!

Dan Loewenherz
quelle
1
@ShauketSheikh hmm, in welchen Situationen funktioniert das nicht?
Dan Loewenherz
Ich habe mit Simulator iOS 8 getestet, es scheint keine Cookies zu senden. Ich habe es doppelt überprüft.
Shauket Sheikh
Ich habe meine Antwort gepostet. Sie können versuchen, @Dan
Shauket Sheikh
7

In iOS 11 können Sie Cookies jetzt verwalten :), siehe diese Sitzung: https://developer.apple.com/videos/play/wwdc2017/220/

Geben Sie hier die Bildbeschreibung ein

Jacky
quelle
2
@ ShobhakarTiwari warum? Gibt es Änderungen in der offiziellen Version von iOS11?
Jacky
Der beste Weg, wenn Sie nur iOS 11 und höher unterstützen. Wenn Sie frühere Versionen unterstützen müssen, verwenden Sie JavaScript, bevor die Seite geladen wird.
PashaN
Dies funktioniert für mich, mit der Ausnahme, dass die setcookie-Methode manchmal ihren Abschluss-Handler NICHT ausführt, was bedeutet, dass meine Webseite manchmal nicht geladen wird - nur auf dem Gerät, beim 3./4 ./5. Mal beim Schließen und erneuten Öffnen die Webansicht, und nachdem es einmal passiert ist, passiert es so lange, bis ich die App zurücksetze - ist jemand auch darauf gestoßen?
Binya Koatz
5

Der Grund für das Posten dieser Antwort ist, dass ich viele Lösungen ausprobiert habe, aber niemand richtig funktioniert. Die meisten Antworten funktionieren nicht, wenn das Cookie zum ersten Mal gesetzt werden muss und das Ergebnis-Cookie beim ersten Mal nicht synchronisiert wird. Verwenden Sie diese Lösung, es funktioniert für beide iOS> = 11.0 <= iOS 11 bis 8.0, funktioniert auch beim ersten Mal mit der Cookie-Synchronisierung.

Für iOS> = 11.0 - Swift 4.2

Holen Sie sich http-Cookies und setzen Sie sie auf diese Weise im wkwebview- Cookie-Store. Es ist sehr schwierig, Ihre Anfrage in wkwebview zu laden , muss Anfrage zum Laden gesendet werden, wenn Cookies vollständig gesetzt werden, hier ist die Funktion, die ich geschrieben habe.

Rufen Sie die Funktion mit Schließung in Vollendung rufen Sie Last Webansicht. Zu Ihrer Information, diese Funktion behandelt nur iOS> = 11.0

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

Hier ist die Implementierung für die syncCookies- Funktion.

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

Für iOS 8 bis iOS 11

Sie müssen einige zusätzliche Dinge einrichten, die Sie benötigen, um zwei Zeit-Cookies mithilfe von WKUserScript zu setzen. Vergessen Sie nicht, auf Anfrage auch Cookies hinzuzufügen. Andernfalls wird Ihr Cookie beim ersten Mal nicht synchronisiert und Ihre Seite wird beim ersten Mal nicht richtig geladen. Dies ist der Teufel, den ich gefunden habe, um Cookies für iOS 8.0 zu unterstützen

bevor Sie Wkwebview Objekterstellung.

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


}

Konzentrieren Sie sich auf diese Funktion 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
}

Hier ist ein weiterer Schritt, bei dem wkuserscript Cookies nicht sofort synchronisiert. Es ist sehr schwierig, die erste Seite mit einem Cookie zu laden. Sie müssen die Webansicht erneut laden, wenn der Prozess beendet wird. Ich empfehle jedoch nicht, sie zu verwenden. Dies ist aus Sicht des Benutzers nicht gut Vergessen Sie nicht, die iOS-Versionsprüfung hinzuzufügen, wenn Sie bereit sind, Anforderungssatz-Cookies in den Anforderungsheader zu laden. Rufen Sie vor der Ladeanforderung diese Funktion auf.

request?.addCookies()

Ich habe eine Erweiterung für URLRequest geschrieben

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

        }
    }

  }
}

Jetzt können Sie iOS> 8 testen

Shauket Sheikh
quelle
2

Finden Sie sofort die Lösung, die höchstwahrscheinlich für Sie funktioniert. Im Grunde ist es modifiziert und aktualisiert für Swift 4 @ user3589213 ‚s Antwort .

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
quelle
1

Ich habe alle oben genannten Antworten ausprobiert, aber keine funktioniert. Nach so vielen Versuchen habe ich endlich einen zuverlässigen Weg gefunden, WKWebview-Cookies zu setzen.

Zuerst müssen Sie eine Instanz von WKProcessPool erstellen und auf die WKWebViewConfiguration setzen, die zum Initialisieren der WkWebview selbst verwendet werden soll:

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

Das Festlegen von WKProcessPool ist hier der wichtigste Schritt. WKWebview nutzt die Prozessisolation - das heißt, es wird auf einem anderen Prozess ausgeführt als der Prozess Ihrer App. Dies kann manchmal zu Konflikten führen und verhindern, dass Ihr Cookie ordnungsgemäß mit dem WKWebview synchronisiert wird.

Schauen wir uns nun die Definition von WKProcessPool an

Der einer Webansicht zugeordnete Prozesspool wird durch die Konfiguration der Webansicht angegeben. Jede Webansicht erhält einen eigenen Webinhaltsprozess, bis ein implementierungsdefiniertes Prozesslimit erreicht ist. Danach teilen Webansichten mit demselben Prozesspool Webinhaltsprozesse.

Beachten Sie den letzten Satz, wenn Sie dieselbe WKWebview für Teilsequenzanforderungen verwenden möchten

Webansichten mit demselben Prozesspool teilen am Ende Webinhaltsprozesse

Ich meine, wenn Sie nicht jedes Mal dieselbe Instanz von WKProcessPool verwenden, wenn Sie eine WKWebView für dieselbe Domäne konfigurieren (möglicherweise haben Sie eine VC A, die eine WKWebView enthält, und Sie möchten verschiedene Instanzen von VC A an verschiedenen Stellen erstellen ) kann es zu Konflikten kommen. Um das Problem zu lösen, speichere ich nach der ersten Erstellung des WKProcessPool für eine WKWebView, die Domäne B lädt, diese in einem Singleton und verwende jedes Mal denselben WKProcessPool, wenn ich eine WKWebView erstellen muss, die dieselbe Domäne B lädt

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

Nach dem Initialisierungsprozess können Sie eine URLRequest in den Abschlussblock von laden httpCookieStore.setCookie. Hier müssen Sie das Cookie an den Anforderungsheader anhängen, sonst funktioniert es nicht.

P / s: Ich habe die Erweiterung aus der fantastischen Antwort von Dan Loewenherz gestohlen

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
quelle
0

Die bessere Lösung für XHR-Anforderungen wird hier gezeigt

Swift 4 Version:

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
quelle
0

Wenn jemand Alamofire verwendet, ist dies die bessere Lösung.

  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
quelle
0

Das funktioniert bei mir: Fügen Sie nach setcookies fetchdatarecords hinzu

   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
quelle
0

Wenn Sie mehrere Cookie-Elemente hinzufügen, können Sie dies folgendermaßen tun: ( path& domainist für jedes Element erforderlich)

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

Andernfalls wird nur das erste Cookie-Element gesetzt.

YanXing Ou
quelle
0

Sie können auch WKWebsiteDataStore verwenden, um ein ähnliches Verhalten wie HTTPCookieStorage von UIWebView zu erhalten.

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

Der folgende Code funktioniert gut in meinem Projekt Swift5. Versuchen Sie, die URL von WKWebView unten zu laden:

    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
quelle
0

Meine Version von Nteiss 'Antwort. Getestet am iOS 11, 12, 13. Sieht aus wie Sie müssen nicht verwenden , DispatchGroupauf iOS 13mehr.

Ich benutze nicht-statische Funktion includeCustomCookiesauf WKWebViewConfiguration, so dass ich aktualisieren kann cookiesjedes Mal , wenn ich neu erstellen 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()
        }
    }
}

Dann benutze ich es so:

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
quelle
0

Dies ist meine Lösung für Cookies und WKWebView in iOS 9 oder höher.

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
quelle
0

Dieser Fehler, den ich gemacht habe, ist, dass ich die gesamte URL im Domain-Attribut übergeben habe. Es sollte nur der Domain-Name sein.

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

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