Bisakah saya mengatur cookie untuk digunakan oleh WKWebView?

135

Saya mencoba mengalihkan aplikasi yang ada dari UIWebViewke WKWebView. Aplikasi saat ini mengelola login / sesi pengguna di luar webviewdan mengatur yang cookiesdiperlukan untuk otentikasi ke dalam NSHTTPCookieStore. Sayangnya baru WKWebViewtidak menggunakan cookiesdari NSHTTPCookieStorage. Apakah ada cara lain untuk mencapai ini?

Kol
sumber

Jawaban:

186

Edit hanya untuk iOS 11+

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

Karena Anda menariknya dari HTTPCookeStorage, Anda dapat melakukan ini:

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

Jawaban lama untuk iOS 10 dan di bawah

Jika Anda meminta cookie Anda diatur pada permintaan pemuatan awal, Anda dapat mengaturnya pada NSMutableURLRequest. Karena cookie hanyalah header permintaan yang diformat khusus, ini dapat dicapai seperti:

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

Jika Anda memerlukan permintaan AJAX berikutnya pada halaman untuk mengatur cookie mereka, ini dapat dicapai dengan hanya menggunakan WKUserScript untuk mengatur nilai-nilai secara terprogram melalui javascript pada awal dokumen seperti:

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

Menggabungkan kedua teknik ini akan memberi Anda cukup alat untuk mentransfer nilai cookie dari Native App Land ke Web View Land. Anda dapat menemukan lebih banyak info tentang API javascript cookie pada halaman Mozilla jika Anda memerlukan cookie yang lebih maju.

Ya, itu menyebalkan bahwa Apple tidak mendukung banyak kebaikan UIWebView . Tidak yakin apakah mereka akan pernah mendukung mereka, tetapi mudah-mudahan mereka akan segera melakukannya. Semoga ini membantu!

mattr
sumber
1
Di mana tempat terbaik untuk menyuntikkan cookie untuk permintaan selanjutnya? Misalnya, memuat halaman awal tercakup dalam jawaban di atas, tetapi bagaimana jika ada tautan pada halaman yang juga mengarah ke domain yang sama, dan juga perlu cookie yang sama disuntikkan ke dalam permintaan? apakah navigasi menu?
Mason G. Zhwiti
1
maaf itu tidak bekerja untuk kamu. Dalam pikiran saya, selama domainnya sama, seharusnya tidak ada masalah. Bisakah Anda mengecek kode yang tautannya tuju ke domain yang sama dengan tempat Anda memuat permintaan? Selain itu, cookie juga dapat dibatasi ke "jalur" tertentu. Mungkin itu menyebabkan beberapa masalah?
mattr
11
Perhatikan bahwa teknik javascript untuk mengatur cookie tidak akan berfungsi untuk cookie "HTTP Only".
Ahmed Nasser
1
Metode di atas berfungsi dengan baik ... tetapi saya bisa melihat cookie digandakan dalam panggilan AJAX berikutnya (Hanya digandakan sekali).
Durga Vundavalli
1
@ Axel92Dev solusi untuk memastikan permintaan pertama yang dibuat dari tampilan web Anda ke server Anda mendapat respons yang secara eksplisit memberitahu tampilan web untuk mengatur cookie lagi dengan bendera HTTPOnly (yaitu: mengatur cookie lagi dalam respon). Anda dapat membuat API khusus untuk tujuan tunggal itu saat menginisialisasi tampilan web, lalu menggunakan tampilan web tersebut secara normal untuk kesuksesan.
Ahmed Nasser
64

Setelah bermain dengan jawaban ini (yang sangat membantu :) kami harus membuat beberapa perubahan:

  • Kami memerlukan tampilan web untuk berurusan dengan banyak domain tanpa membocorkan informasi cookie pribadi antara domain tersebut
  • Kami membutuhkannya untuk menghormati cookie yang aman
  • Jika server mengubah nilai cookie, kami ingin aplikasi kami mengetahuinya NSHTTPCookieStorage
  • Jika server mengubah nilai cookie, kami tidak ingin skrip kami meresetnya kembali ke nilai aslinya ketika Anda mengikuti tautan / AJAX dll.

Jadi kami memodifikasi kode kami menjadi ini;

Membuat permintaan

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

Ini memastikan bahwa permintaan pertama memiliki cookie yang tepat, tanpa mengirim cookie apa pun dari penyimpanan bersama yang untuk domain lain, dan tanpa mengirim cookie aman apa pun ke dalam permintaan yang tidak aman.

Berurusan dengan permintaan lebih lanjut

Kami juga perlu memastikan bahwa permintaan lain telah menetapkan cookie. Ini dilakukan dengan menggunakan skrip yang dijalankan pada pemuatan dokumen yang memeriksa untuk melihat apakah ada cookie yang diatur dan jika tidak, setel ke nilai dalam 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];

Berurusan dengan perubahan cookie

Kita juga harus berurusan dengan server yang mengubah nilai cookie. Ini berarti menambahkan skrip lain untuk memanggil kembali dari tampilan web yang kami buat untuk memperbarui skrip kami 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"];

dan menerapkan metode delegasi untuk memperbarui cookie apa pun yang telah berubah, memastikan bahwa kami hanya memperbarui cookie dari domain saat ini!

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

Ini tampaknya memperbaiki masalah cookie kami tanpa harus berurusan dengan setiap tempat yang kami gunakan WKWebView secara berbeda. Kita sekarang dapat menggunakan kode ini sebagai pembantu untuk membuat tampilan web kita dan secara transparan memperbarui NSHTTPCookieStorageuntuk kita.


EDIT: Ternyata saya menggunakan kategori pribadi di NSHTTPCookie - inilah kodenya:

- (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;
}
dekanWombourne
sumber
6
Saya telah membungkus kode Anda menjadi subkelas WKWebView. Jangan ragu untuk memeriksanya github.com/haifengkao/YWebView
Hai Feng Kao
Bagaimana jika cookie Anda mengandung = tanda pada nilainya? Apakah ini akan berhasil?
iOSDitandai
@ iOS Dianggap saya pikir begitu. Jika nilai a=bAnda, Anda akan berakhir dengan string cookie name=a=b;domain=.example.com;path=/- Saya percaya bahwa standar terpecah ;dan kemudian terpecah pada yang pertama = di pasangan kunci = nilai. Saya akan menguji ini :)
deanWombourne
respons Anda banyak membantu saya, namun saya ingin menambahkan sesuatu ke posting Anda, ada beberapa risiko ketika menggunakan metode pembaruan Anda, beberapa kerangka kerja JS mungkin membuat cookie yang memiliki nama yang sama tetapi domain yang berbeda, dan jika Anda mencoba memperbaruinya menggunakan metode js Anda memiliki risiko tinggi memperbarui cookie dengan nilai yang salah. Juga bagi kami string cookie js, harus dicopot dari bendera amannya, karena server kami membuat pengalihan tidak baik antara http dan https, menyebabkan cookie aman tidak ada di beberapa halaman dalam beberapa kasus tepi yang tidak menyenangkan.
RicardoDuarte
Saya benar-benar berpikir perusahaan tempat saya bekerja ketika saya menulis ini memang harus menambahkan beberapa perlindungan domain setelah ditayangkan. Kami tidak pernah (afaik) berlari ke masalah aman / tidak aman - terdengar seperti mimpi buruk!
DeanWombourne
42

Cookie harus diatur pada konfigurasi sebelum WKWebView dibuat. Jika tidak, bahkan dengan WKHTTPCookieStore's setCookieselesai handler, cookie akan tidak andal disinkronisasikan ke tampilan web. Ini kembali ke baris ini dari dokumen padaWKWebViewConfiguration

@NSCopying var configuration: WKWebViewConfiguration { get }

Itu @NSCopyingadalah salinan yang dalam. Implementasinya di luar saya, tetapi hasil akhirnya adalah kecuali Anda menetapkan cookie sebelum menginisialisasi tampilan web, Anda tidak dapat mengandalkan cookie yang ada di sana. Ini dapat memperumit arsitektur aplikasi karena menginisialisasi tampilan menjadi proses yang tidak sinkron. Anda akan berakhir dengan sesuatu seperti ini

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

dan kemudian menggunakannya seperti

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

Contoh di atas menolak pembuatan tampilan hingga saat terakhir yang memungkinkan, solusi lain adalah membuat konfigurasi atau tampilan web jauh sebelumnya dan menangani sifat asinkron sebelum membuat pengendali tampilan.

Catatan terakhir: setelah Anda membuat tampilan web ini, Anda membuatnya longgar, Anda tidak dapat menambahkan lebih banyak cookie tanpa menggunakan metode yang dijelaskan dalam jawaban ini . Namun Anda dapat menggunakan WKHTTPCookieStoreObserverapi untuk setidaknya mengamati perubahan yang terjadi pada cookie. Jadi jika cookie sesi diperbarui di tampilan web, Anda dapat secara manual memperbarui sistem HTTPCookieStoragedengan cookie baru ini jika diinginkan.

Untuk informasi lebih lanjut tentang ini, lanjutkan ke 18:00 di ini Sesi Pemuatan Konten Web Kustom Sesi WWDC 2017 . Pada awal sesi ini, ada contoh kode menipu yang menghilangkan fakta bahwa tampilan web harus dibuat dalam penangan penyelesaian.

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

Demo langsung pukul 18:00 mengklarifikasi ini.

Sunting Pada Mojave Beta 7 dan iOS 12 Beta 7 setidaknya, saya melihat perilaku yang jauh lebih konsisten dengan cookie. The setCookie(_:)Metode bahkan muncul jadi pengaturan cookie setelah WKWebViewtelah dibuat. Aku menemukan itu meskipun penting, untuk tidak menyentuh pada processPoolvariabel sama sekali. Fungsionalitas pengaturan cookie bekerja paling baik ketika tidak ada kolam tambahan yang dibuat dan ketika properti itu dibiarkan begitu saja. Saya pikir aman untuk mengatakan bahwa kami mengalami masalah karena beberapa bug di WebKit.

nteissler
sumber
Sepertinya penanganan / pengaturan cookie lebih dapat diandalkan di Mojave 10.14 beta 3 dan iOS 12 beta 3
nteissler
6
Sangat mendalam dan jawaban yang diremehkan
Nicolás Carrasco
1
Saya masih mengalami masalah ini di iOS 12 dengan WKWebView yang sudah dimuat. Kadang-kadang setCookie () sebenarnya akan disinkronkan dengan WKWebView segera, kadang-kadang itu tidak akan membuat penanganannya agak sporadis
bmjohns
Saya masih melihat masalah juga sejak radar diklaim diperbaiki, tetapi jauh lebih jarang. Seberapa sering Anda melihat kegagalan cookie? Jika Anda memiliki proyek yang cukup kecil yang dapat direproduksi, saya sangat merekomendasikan untuk mengirimkan bug webkit di sini: webkit.org/reporting-bugs Anda juga dapat men-tweet Brady Eidson (baik) seorang arsitek webkit di apple yang sangat responsif terhadap jenis-jenis ini. laporan dan bug.
nteissler
ini jawaban yang benar - tidak perlu menerjemahkan cookie secara manual sebagai bidang tajuk di setiap URLRequest, hanya saja setCookie () perlu digunakan seperti dijelaskan di sini.
Guillaume Laurent
25

bekerja untukku

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)
    }
}
pengguna3589213
sumber
Retas yang mengagumkan, terutama bagaimana iOS yang keras kepala tentang menimpa cookie di WKWebView yang ada. Satu-satunya Gotcha adalah bahwa WKNavigationKey sebelumnya telah menjadi basi. Kode lain mungkin menunggu dengan sia-sia pada kode lama.
BaseZen
2
Apakah ini benar? Menghargai itu mungkin berhasil dalam beberapa keadaan. Namun, tanggung jawab metode delegasi ini - decisionPolicyForNavigationAction - adalah memutuskan kebijakan; tidak benar-benar memuat permintaan. Itu sudah dimulai sebelumnya. Dalam hal ini, apakah ini tidak menyebabkan permintaan dimuat dua kali?
Max MacLeod
2
@ MaxMacLeod Dalam elsekondisi dia memanggil decisionHandlerpenutupan dengan .cancelbegitu webviewsebenarnya tidak memuat permintaan awal. Setelah loadRequestdipanggil dalam elsekondisi metode delegasi ini akan dipanggil lagi untuk permintaan itu dan itu akan masuk ke ifkondisi karena Cookieheader akan hadir.
halil_g
2
Meskipun ini tidak akan berfungsi ketika permintaan awal sudah memiliki beberapa cookie yang ditetapkan, karena tidak akan pernah masuk ke elsekondisi.
halil_g
Perhatikan bahwa ini adalah 1) Tidak berfungsi untuk setiap situasi - misalnya, kasus ketika tampilan web memuat bingkai 2) Tidak aman - itu dapat mengirim cookie dengan informasi sensitif ke URL pihak ketiga
Peter Prokop
20

Ini adalah versi solusi Mattrs saya di Swift untuk menyuntikkan semua cookie dari HTTPCookieStorage. Ini dilakukan terutama untuk menyuntikkan cookie otentikasi untuk membuat sesi pengguna.

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
sumber
lebih baik menambahkan baris ini untuk memastikan format lokal sudah benar:dateFormatter.locale = NSLocale(localeIdentifier: "en_US_POSIX")
bubuxu
di mana memanggil ini?
markhorrocks
Ini bekerja dengan baik untuk saya di Swift 4 (dengan sedikit penyesuaian)
Frédéric Adda
Ini berfungsi baik untuk saya, tetapi hanya kedua kalinya saya mengunjungi situs (pertama kali cookie tidak disetel) - ada yang menemukan ini?
MrChrisBarker
Muatan
10

mengatur cookie

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

hapus cookie

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

Pembaruan 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 yang dalam
sumber
1
Hai, bisakah Anda menambahkan kode untuk mendapatkan cookie HTTPCookieStorage.sharedjuga?
markhorrocks
Ini adalah satu-satunya cara saya mendapatkan WKWebView untuk menambahkan cookie ke setiap permintaan yang dibuat oleh tampilan web
Chicowitz
Jika itu berisi httponly cookie sebagai tanggapan, Anda tidak bisa mendapatkan nilai cookie dengan cara ini.
Otak
1
ini hanya melakukan set cookie kembali ke penyimpanan httpcookies di mana kode yang mengatur cookie untuk wkwebview?
Shauket Sheikh
8

Setelah melihat berbagai jawaban di sini dan tidak berhasil, saya menyisir dokumentasi WebKit dan menemukan requestHeaderFieldsmetode statis HTTPCookie, yang mengubah array cookie menjadi format yang cocok untuk bidang header. Menggabungkan ini dengan wawasan mattr untuk memperbaruiURLRequest sebelum memuatnya dengan header cookie membuat saya melewati garis finish.

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)

Untuk membuatnya lebih sederhana, gunakan ekstensi:

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

Sekarang menjadi:

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

Ekstensi ini juga tersedia di LionheartExtensions jika Anda hanya ingin solusi drop-in. Bersulang!

Dan Loewenherz
sumber
1
@ShauketSheikh hmm, dalam situasi apa ini tidak berhasil?
Dan Loewenherz
Saya diuji menggunakan simulator ios 8 tampaknya tidak mengirim cookie. saya sudah memeriksanya.
Shauket Sheikh
saya memposting jawaban saya, Anda dapat mencoba
@Dan
7

Di iOS 11, Anda dapat mengelola cookie sekarang :), lihat sesi ini: https://developer.apple.com/videos/play/wwdc2017/220/

masukkan deskripsi gambar di sini

Jacky
sumber
2
@ShobhakarTiwari kenapa? apakah ada perubahan yang terjadi pada rilis resmi iOS11?
Jacky
Cara terbaik untuk pergi jika Anda hanya mendukung iOS 11 dan lebih tinggi, jika Anda perlu mendukung versi sebelumnya, gunakan JavaScript sebelum memuat halaman.
PashaN
Ini berfungsi untuk saya, kecuali kenyataan bahwa kadang-kadang metode setcookie TIDAK menjalankan penangan penyelesaiannya, yang berarti kadang-kadang halaman web saya tidak memuat - hanya terjadi pada perangkat, terjadi waktu penutupan dan pembukaan kembali tanggal 3/4/5 dan kembali. tampilan web, dan setelah itu terjadi sekali, itu terus terjadi sampai saya mengatur ulang aplikasi - ada yang juga mengalami ini?
Binya Koatz
5

Alasan di balik memposting jawaban ini adalah saya mencoba banyak solusi tetapi tidak ada yang berfungsi dengan baik, sebagian besar jawabannya tidak berfungsi jika harus mengatur cookie pertama kali, dan mendapatkan cookie hasil tidak disinkronkan pertama kali, Silakan gunakan solusi ini berfungsi untuk keduanya iOS> = 11.0 <= iOS 11 hingga 8.0, juga berfungsi dengan sinkronisasi cookie pertama kali.

Untuk iOS> = 11.0 - Swift 4.2

Dapatkan http cookies dan atur di wkwebview cookie store seperti ini, ini sangat sulit untuk memuat permintaan Anda di wkwebview , harus mengirim permintaan untuk memuat ketika cookie akan diset sepenuhnya, di sini adalah fungsi yang saya tulis.

Fungsi panggilan dengan penutupan selesai Anda sebut memuat tampilan web. FYI fungsi ini hanya menangani iOS> = 11.0

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

Berikut ini adalah implementasi untuk fungsi 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()
}
}

Untuk iOS 8 hingga iOS 11

Anda perlu mengatur beberapa hal tambahan yang Anda perlu mengatur dua cookie waktu satu melalui menggunakan WKUserScript dan jangan lupa untuk menambahkan cookie dalam permintaan juga, jika cookie Anda tidak disinkronkan pertama kali dan Anda akan melihat halaman Anda tidak memuat pertama kali dengan benar. ini adalah hal yang saya temukan untuk mendukung cookie untuk iOS 8.0

sebelum Anda membuat objek 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))


}

Fokus pada fungsi ini 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
}

Berikut ini langkah lain wkuserscript tidak menyinkronkan cookie segera, ada banyak cara untuk memuat halaman pertama kali dengan cookie satu adalah untuk memuat kembali tampilan web jika menghentikan proses tetapi saya tidak merekomendasikan untuk menggunakannya, itu tidak baik untuk sudut pandang pengguna , heck adalah setiap kali Anda siap untuk memuat cookie set permintaan di header permintaan juga seperti ini, jangan lupa untuk menambahkan cek versi iOS. sebelum memuat permintaan, panggil fungsi ini.

request?.addCookies()

saya menulis ekstensi untuk 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

        }
    }

  }
}

sekarang Anda siap untuk menguji iOS> 8

Syaikh Syaikh
sumber
2

Silakan temukan solusi yang kemungkinan besar akan berhasil untuk Anda di luar kotak. Pada dasarnya itu diubah dan diperbarui untuk Swift 4 @ user3589213 's jawaban .

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

Saya sudah mencoba semua jawaban di atas tetapi tidak satupun yang berhasil. Setelah begitu banyak upaya saya akhirnya menemukan cara yang dapat diandalkan untuk mengatur cookie WKWebview.

Pertama, Anda harus membuat turunan dari WKProcessPool dan mengaturnya ke konfigurasi WKWebView yang akan digunakan untuk menginisialisasi WkWebview itu sendiri:

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

Pengaturan WKProcessPool adalah langkah paling penting di sini.WKWebview memanfaatkan proses isolasi - yang artinya berjalan pada proses yang berbeda dari proses aplikasi Anda. Ini kadang-kadang dapat menyebabkan konflik dan mencegah cookie Anda tidak disinkronkan dengan benar dengan WKWebview.

Sekarang mari kita lihat definisi WKProcessPool

Kumpulan proses yang terkait dengan tampilan web ditentukan oleh konfigurasi tampilan webnya. Setiap tampilan web diberi proses Konten Web sendiri hingga batas proses yang ditentukan implementasi tercapai; setelah itu, tampilan web dengan kumpulan proses yang sama akhirnya berbagi proses Konten Web.

Perhatikan kalimat terakhir jika Anda berencana untuk menggunakan WKWebview yang sama untuk permintaan selanjutnya

tampilan web dengan kumpulan proses yang sama pada akhirnya berbagi proses Konten Web

apa yang saya maksudkan adalah bahwa jika Anda tidak menggunakan instance WKProcessPool yang sama setiap kali Anda mengkonfigurasi WKWebView untuk domain yang sama (mungkin Anda memiliki VC A yang berisi WKWebView dan Anda ingin membuat instance VC A yang berbeda di tempat yang berbeda ), mungkin ada cookie pengaturan konflik. Untuk mengatasi masalah ini, setelah pembuatan pertama WKProcessPool untuk WKWebView yang memuat domain B, saya menyimpannya dalam singleton dan menggunakan WKProcessPool yang sama setiap kali saya harus membuat WKWebView yang memuat domain B yang sama

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

Setelah proses inisialisasi, Anda dapat memuat URLRequest dalam blok selesai dari httpCookieStore.setCookie. Di sini, Anda harus melampirkan cookie ke header permintaan jika tidak akan berhasil.

P / s: Saya mencuri ekstensi dari jawaban fantastis di atas oleh 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
sumber
1

Versi saya dari jawaban nteiss. Diuji pada iOS 11, 12, 13. Terlihat seperti Anda tidak harus menggunakan DispatchGroupdi iOS 13lagi.

Saya menggunakan fungsi non-statis includeCustomCookiesaktif WKWebViewConfiguration, sehingga saya dapat memperbarui cookiessetiap kali saya membuat yang baru 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()
        }
    }
}

Maka saya menggunakannya seperti ini:

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

Perbaikan yang lebih baik untuk permintaan XHR ditunjukkan di sini

Versi 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
sumber
0

Jika ada yang menggunakan Alamofire, maka ini adalah solusi yang lebih baik.

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

Ini berfungsi untuk saya: Setelah setcookies, tambahkan 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
sumber
0

Saat menambahkan item kuki multipel, Anda dapat melakukannya seperti ini: ( path& domaindiperlukan untuk setiap item)

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

jika tidak, hanya item cookie pertama yang akan ditetapkan.

YanXing Ou
sumber
0

Anda juga dapat menggunakan WKWebsiteDataStore untuk mendapatkan perilaku yang mirip dengan HTTPCookieStorage dari UIWebView.

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

Kode di bawah ini berfungsi dengan baik di proyek saya Swift5. coba muat url oleh WKWebView di bawah ini:

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

Ini adalah solusi saya untuk menangani dengan Cookie dan WKWebView di iOS 9 atau lebih baru.

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

Kesalahan ini yang saya lakukan adalah saya melewati seluruh url dalam atribut domain, seharusnya hanya nama domain.

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

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