Могу ли я установить файлы cookie, которые будут использоваться WKWebView?

135

Я пытаюсь переключить существующее приложение с UIWebViewна WKWebView. Текущее приложение управляет входом / сеансом пользователей вне webviewи устанавливает cookiesнеобходимые для аутентификации в NSHTTPCookieStore. К сожалению, новый WKWebViewне использует cookiesиз NSHTTPCookieStorage. Есть ли другой способ добиться этого?

седло
источник

Ответы:

186

Редактировать только для iOS 11+

Используйте 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)

Поскольку вы извлекаете их из HTTPCookeStorage, вы можете сделать это:

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

Старый ответ для iOS 10 и ниже

Если вам требуется, чтобы ваши куки были установлены при первоначальном запросе загрузки, вы можете установить их в NSMutableURLRequest. Поскольку файлы cookie - это просто специально отформатированный заголовок запроса, это может быть достигнуто следующим образом:

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

Если вам требуется, чтобы в последующих запросах AJAX на странице были установлены файлы cookie, это можно сделать, просто используя WKUserScript для программной установки значений с помощью javascript при запуске документа следующим образом:

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

Объединение этих двух методов должно дать вам достаточно инструментов для переноса значений файлов cookie из Native App Land в Web View Land. Вы можете найти дополнительную информацию об API cookie javascript на странице Mozilla, если вам нужны более продвинутые файлы cookie.

Да, это отстой, что Apple не поддерживает многие тонкости UIWebView . Не уверен, будут ли они когда-либо поддерживать их, но, надеюсь, они скоро доберутся до этого. Надеюсь это поможет!

mattr
источник
1
Где лучше всего вводить файлы cookie для последующих запросов? Например, начальная загрузка страницы описана в ответе выше, но что, если на странице есть ссылки, которые также ведут на тот же домен и также требуют использования тех же файлов cookie в запросе? didStartProvisionalNavigation?
Мейсон Дж. Жвити,
1
извините, это не работает для вас. На мой взгляд, до тех пор, пока домены одинаковы, не должно быть никаких проблем. Можете ли вы дважды проверить код, на который указывает ссылка на тот же домен, с которого вы загрузили запрос? Кроме того, куки могут быть ограничены определенным «путем». Может быть, это вызывает некоторые проблемы?
mattr
11
Обратите внимание, что метод javascript для установки файлов cookie не будет работать для файлов cookie «только HTTP».
Ахмед Насер
1
Вышеуказанный метод работает отлично ... но я мог видеть, что куки дублируются в последующих вызовах AJAX (дублируются только один раз).
Дурга Вундавалли
1
@ Axel92. Чтобы обойти эту проблему, убедитесь, что первый запрос, сделанный вашим веб-представлением на ваш сервер, получит ответ, который явно скажет веб-представлению снова установить файлы cookie с флагом HTTPOnly (т. Е. Снова установить файлы cookie в ответе). Вы можете создать специальный API-интерфейс для этой единственной цели при инициализации веб-просмотра, а затем, как правило, использовать веб-просмотр в случае успеха.
Ахмед Насер
64

После игры с этим ответом (который был фантастически полезен :) нам пришлось внести несколько изменений:

  • Нам нужны веб-представления для работы с несколькими доменами без утечки частной информации cookie между этими доменами.
  • Нам нужно, чтобы соблюдать безопасные куки
  • Если сервер изменяет значение cookie, мы хотим, чтобы наше приложение узнало об этом в NSHTTPCookieStorage
  • Если сервер изменяет значение cookie, мы не хотим, чтобы наши сценарии сбрасывали его обратно в исходное значение, когда вы переходите по ссылке / AJAX и т. Д.

Таким образом, мы изменили наш код, чтобы быть этим;

Создание запроса

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

Это гарантирует, что в первом запросе установлены правильные файлы cookie, без отправки файлов cookie из общего хранилища, предназначенных для других доменов, и без отправки каких-либо безопасных файлов cookie в незащищенный запрос.

Работа с дальнейшими запросами

Мы также должны убедиться, что для других запросов установлены файлы cookie. Это делается с помощью скрипта, который запускается при загрузке документа, который проверяет, есть ли набор файлов cookie, и если нет, задает для него значение в 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];

Работа с изменениями cookie

Нам также нужно иметь дело с сервером, меняющим значение куки. Это означает добавление другого скрипта для обратного вызова из веб-представления, которое мы создаем для обновления нашего 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"];

и реализуя метод делегата для обновления любых файлов cookie, которые изменились, убедившись, что мы обновляем файлы cookie только из текущего домена!

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

Кажется, это решает наши проблемы с файлами cookie, и нам не приходится иметь дело с каждым местом, в котором мы используем WKWebView. Теперь мы можем просто использовать этот код в качестве помощника для создания наших веб-представлений, и он прозрачно обновляется NSHTTPCookieStorageдля нас.


РЕДАКТИРОВАТЬ: Оказывается, я использовал частную категорию на NSHTTPCookie - вот код:

- (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
источник
6
Я обернул ваш код в подкласс WKWebView. Не стесняйтесь проверить это github.com/haifengkao/YWebView
Хай Фенг Као
Что, если ваши куки содержат = знаки в значении? Будет ли это работать?
iOSAddicted
@iOSAddicted Я так думаю . Если a=bбы вы имели значение, то вы бы получили строку cookie name=a=b;domain=.example.com;path=/- я считаю, что стандарт разделяется, ;а затем разделяется на первый = в паре ключ = значение. Хотя я бы
проверил
Ваш ответ мне очень помог, однако я хотел бы добавить что-то к вашему сообщению, при использовании вашего метода обновления есть несколько рисков, некоторые платформы JS могут создавать куки-файлы с тем же именем, но другим доменом, и если вы попытаетесь обновить его используя методы js, вы рискуете обновить cookie с неправильным значением. Также для нас нужно было удалить строку cookie js из ее безопасного флага, так как наш сервер делает зловещие перенаправления между http и https, в результате чего безопасные куки не присутствуют на некоторых страницах в некоторых неприятных крайних случаях.
Рикардо Дуарте,
Я действительно думаю, что компания, с которой я был, когда писал это, должна была добавить некоторую защиту домена после того, как она была запущена. Мы никогда (afaik) не сталкивались с проблемой безопасности / незащищенности - звучит как кошмар!
Дин Уомбурн,
42

Файлы cookie должны быть установлены в конфигурации до WKWebViewсоздания. В противном случае, даже с WKHTTPCookieStore«S setCookieобработчик завершения, печенье не будет надежно синхронизироваться с веб - просмотра. Это восходит к этой строке из документов наWKWebViewConfiguration

@NSCopying var configuration: WKWebViewConfiguration { get }

Это @NSCopyingчто-то вроде глубокой копии. Реализация выше моего понимания, но конечный результат заключается в том, что если вы не установите файлы cookie перед инициализацией веб-просмотра, вы не сможете рассчитывать на наличие файлов cookie. Это может усложнить архитектуру приложения, поскольку инициализация представления становится асинхронным процессом. Вы закончите с чем-то вроде этого

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

а затем использовать что-то вроде

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

Приведенный выше пример откладывает создание представления до последнего возможного момента, другим решением было бы заранее создать конфигурацию или веб-представление и обработать асинхронную природу до создания контроллера представления.

Последнее замечание: как только вы создали это веб-представление, вы оставили его в свободном доступе, вы не можете добавить больше файлов cookie без использования методов, описанных в этом ответе . Тем не менее, вы можете использовать WKHTTPCookieStoreObserverAPI, чтобы, по крайней мере, наблюдать за изменениями, происходящими с файлами cookie. Таким образом, если файл cookie сеанса обновляется в веб-просмотре, вы можете вручную обновить систему HTTPCookieStorageс помощью этого нового файла cookie, если это необходимо.

Более подробную информацию можно найти в 18:00 на этой веб-загрузке пользовательского веб-контента, посвященной сессии WWDC 2017 года . В начале этого сеанса есть пример вводящего в заблуждение кода, в котором пропускается тот факт, что веб-просмотр должен быть создан в обработчике завершения.

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

Живая демонстрация в 18:00 разъясняет это.

Редактировать На Мохаве Beta 7 и IOS 12 Beta 7 , по крайней мере, я вижу гораздо более последовательное поведение с печеньем. Этот setCookie(_:)метод даже позволяет разрешить установку файлов cookie после WKWebViewсоздания. Я нашел это важно , хотя, чтобы не прикоснуться к processPoolпеременной вообще. Функциональность настройки cookie работает лучше всего, когда не создаются дополнительные пулы и когда это свойство оставлено в покое. Я думаю, можно с уверенностью сказать, что у нас были проблемы из-за некоторых ошибок в WebKit.

nteissler
источник
Похоже, что обработка / настройка
файлов
6
Очень глубокий и недооцененный ответ
Николас Карраско
1
У меня все еще есть эта проблема в iOS 12 с уже загруженным WKWebView. Иногда setCookie () фактически сразу синхронизируется с WKWebView, иногда это не делает обработку несколько спорадической
bmjohns
Я все еще видел проблемы, так как радар был исправлен, но гораздо реже. Как часто вы видите сбой cookie? Если у вас есть воспроизводимый, достаточно маленький проект, я действительно рекомендую отправить сообщение об ошибке webkit здесь: webkit.org/reporting-bugs Вы также можете написать в Twitter Брэди Эйдсона (приятно), архитектора webkit в apple, который очень отзывчив на эти виды отчеты и ошибки.
nteissler
это правильный ответ - нет необходимости вручную переводить куки в качестве полей заголовка в каждом URLRequest, просто нужно использовать setCookie (), как описано здесь.
Гийом Лоран
25

работать на меня

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
источник
Удивительный хак, особенно то, как непримиримая iOS об отмене cookie в существующем WKWebView. Единственный недостаток в том, что предыдущий WKNavigationKey стал устаревшим. Другой код может ждать напрасно на старом.
BaseZen
2
это верно? Примите во внимание, что при некоторых обстоятельствах это может сработать. Однако ответственность этого метода делегата - DecisionPolicyForNavigationAction - заключается в определении политики; не загружать запрос. Это было инициировано ранее. В каком случае это не приводит к двойной загрузке запроса?
Макс МакЛауд
2
@MaxMacLeod В elseсостоянии, с которым он вызывает decisionHandlerзакрытие, .cancelпоэтому webviewфактически не загружает первоначальный запрос. После loadRequestвызова в elseусловии этот метод делегата будет вызван снова для этого запроса, и он перейдет в ifусловие, потому что Cookieбудет присутствовать заголовок.
halil_g
2
Хотя это не сработает, если в исходном запросе уже установлены файлы cookie, поскольку он никогда не перейдет в elseусловие.
halil_g
Обратите внимание, что это 1) Не работает для каждой ситуации - например, в случае, когда веб-просмотр загружает фреймы 2) Небезопасно - он может отправлять cookie с конфиденциальной информацией на сторонний URL
Питер Прокоп
20

Вот моя версия решения Mattrs в Swift для внедрения всех файлов cookie из HTTPCookieStorage. Это было сделано в основном для того, чтобы внедрить файл cookie аутентификации для создания сеанса пользователя.

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
}
Миша
источник
лучше добавить эту строку, чтобы убедиться, что форматирование локали правильное:dateFormatter.locale = NSLocale(localeIdentifier: "en_US_POSIX")
bubuxu
как это назвать?
markhorrocks
Это отлично сработало для меня в Swift 4 (с небольшими изменениями)
Фредерик Адда
Это отлично работает для меня, но только во второй раз, когда я захожу на сайт (первый раз, когда файлы cookie не устанавливаются) - кто-нибудь сталкивался с этим?
MrChrisBarker
Первая загрузка дает ошибку, вторая загрузка работает :( В чем может быть проблема?
Шаукет Шейх
10

установить cookie

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

удалить cookie

self.webView.evaluateJavaScript("document.cookie='access_token=';domain='your domain';") { (data, error) -> Void in
        self.webView.reload()
}
cycDroid
источник
9

Обновление 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)
    }
}
Deep Parekh
источник
1
Привет, а можно ли добавить код для получения файлов cookie HTTPCookieStorage.shared?
markhorrocks 07
Это единственный способ заставить WKWebView добавлять файлы cookie к каждому запросу, сделанному веб-просмотром,
Чиковиц,
Если в ответ он содержит httponly cookie, вы не сможете получить значение cookie таким образом.
мозг
1
это только установка файлов cookie обратно в хранилище httpcookies, где находится код, который устанавливает файлы cookie для wkwebview?
Шаукет Шейх
8

Просмотрев здесь различные ответы и не добившись успеха, я просмотрел документацию WebKit и наткнулся на requestHeaderFieldsстатический метод HTTPCookie, который преобразует массив файлов cookie в формат, подходящий для поля заголовка. Объединив это с пониманием mattr обновленияURLRequest файла перед загрузкой с заголовками cookie, я добрался до финиша.

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)

Чтобы сделать это еще проще, используйте расширение:

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

Теперь это просто становится:

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

Это расширение также доступно в LionheartExtensions, если вам просто нужно решение для прямого подключения. Ура!

Дэн Левенхерц
источник
1
@ShauketSheikh хм, в каких ситуациях это не работает?
Дэн Лёвенгерц
Я тестировал с помощью симулятора ios 8, похоже, он не отправляет файлы cookie. Я перепроверил это.
Шаукет Шейх
я опубликовал свой ответ, вы можете попробовать @Dan
Sheikh
7

В iOS 11 теперь вы можете управлять файлами cookie :), см. Этот сеанс: https://developer.apple.com/videos/play/wwdc2017/220/

введите описание изображения здесь

Jacky
источник
2
@ShobhakarTiwari почему? происходят ли какие-либо изменения в официальном выпуске iOS11?
Джеки
Лучший способ пойти, если вы поддерживаете только iOS 11 и выше, если вам нужно поддерживать предыдущие версии, используйте JavaScript перед загрузкой страницы.
PashaN
Это работает для меня, за исключением того факта, что иногда метод setcookie НЕ запускает свой обработчик завершения, что означает, что иногда моя веб-страница не загружается - происходит только на устройстве, происходит 3-й / 4-й / 5-й раз при закрытии и повторном открытии веб-просмотр, и после того, как это произойдет один раз, это будет продолжаться до тех пор, пока я не перезагружу приложение - кто-нибудь также сталкивается с этим?
Binya Koatz
5

Причина, по которой опубликован этот ответ, заключается в том, что я пробовал много решений, но никто не работал должным образом, большая часть ответа не работает в случае, когда нужно установить файл cookie в первый раз, и файл cookie результата не синхронизируется в первый раз, пожалуйста, используйте это решение, оно работает для обоих iOS> = 11.0 <= iOS 11 до 8.0, также работайте с синхронизацией файлов cookie в первый раз.

Для iOS> = 11.0 - Swift 4.2

Получите http cookie и установите в хранилище cookie wkwebview таким образом, очень сложно загрузить ваш запрос в wkwebview , необходимо отправить запрос на загрузку, когда файлы cookie будут полностью установлены, вот функция, которую я написал.

Вызов функции с закрытием в завершении вы вызываете загрузку веб-просмотра. FYI, эта функция обрабатывает только iOS> = 11.0

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

Вот реализация функции 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()
}
}

Для iOS 8 - iOS 11

вам нужно настроить некоторые дополнительные вещи, вам нужно установить два временных файла cookie, используя WKUserScript и не забудьте также добавить файлы cookie в запрос, иначе ваш файл cookie не синхронизируется в первый раз, и вы увидите, что ваша страница не загружается должным образом в первый раз. это черт возьми, что я обнаружил, что поддерживает файлы cookie для iOS 8.0

перед созданием объекта 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))


}

Сосредоточьтесь на этой функции get JSCookiesString

 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
}

Вот еще один шаг, который wkuserscript не синхронизирует файлы cookie сразу, там много чертов, чтобы загрузить первую страницу с файлом cookie, нужно снова перезагрузить веб-просмотр, если он завершит процесс, но я не рекомендую его использовать, это не хорошо для точки зрения пользователя , черт возьми, когда вы готовы загрузить файлы cookie набора запросов в заголовок запроса, не забудьте добавить проверку версии iOS. перед запросом загрузки вызовите эту функцию.

request?.addCookies()

я написал расширение для 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

        }
    }

  }
}

Теперь вы готовы к тестированию iOS> 8

Шаукет Шейх
источник
2

Пожалуйста, найдите решение, которое, скорее всего, подойдет вам прямо из коробки. В основном это изменено и обновлено для Swift 4 @ user3589213 «s ответа .

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

Я пробовал все ответы выше, но ни один из них не работает. После стольких попыток я наконец нашел надежный способ установить cookie WKWebview.

Сначала вам нужно создать экземпляр WKProcessPool и установить для него WKWebViewConfiguration, который будет использоваться для инициализации самого WkWebview:

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

Настройка WKProcessPool - самый важный шаг здесь.WKWebview использует изоляцию процессов - это означает, что он работает в другом процессе, чем процесс вашего приложения. Иногда это может вызвать конфликт и помешать правильной синхронизации вашего файла cookie с WKWebview.

Теперь давайте посмотрим на определение WKProcessPool

Пул процессов, связанный с веб-представлением, определяется его конфигурацией веб-представления. Каждому веб-представлению предоставляется свой собственный процесс веб-содержимого до тех пор, пока не будет достигнут предел процесса, определенный реализацией; после этого веб-представления с одним и тем же пулом процессов в конечном итоге разделяют процессы веб-содержимого.

Обратите внимание на последнее предложение, если вы планируете использовать тот же WKWebview для запросов подпоследовательности.

веб-представления с одним и тем же пулом процессов в конечном итоге разделяют процессы веб-содержимого

Я имею в виду, что если вы не используете один и тот же экземпляр WKProcessPool каждый раз, когда настраиваете WKWebView для одного и того же домена (возможно, у вас есть VC A, который содержит WKWebView, и вы хотите создать разные экземпляры VC A в разных местах ), возможны конфликты настроек cookie. Чтобы решить эту проблему, после первого создания WKProcessPool для WKWebView, загружающего домен B, я сохраняю его в синглтоне и использую тот же самый WKProcessPool каждый раз, когда мне нужно создать WKWebView, который загружает тот же домен 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
}()

После завершения процесса инициализации, вы можете загрузить URLRequest внутри заканчивания блока из httpCookieStore.setCookie. Здесь вы должны прикрепить файл cookie к заголовку запроса иначе он не будет работать.

P / s: Я украл расширение из фантастического ответа Дэна Лёвенгерца выше

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

Моя версия ответа nteiss. Проверено на iOS 11, 12, 13. Похоже , вы не должны использовать DispatchGroupна iOS 13больше.

Я использую нестатическую функцию includeCustomCookieson WKWebViewConfiguration, чтобы я мог обновлять cookiesкаждый раз, когда создаю новый 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()
        }
    }
}

Тогда я использую это так:

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

Лучшее исправление для запросов XHR показано здесь.

Версия 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

Если кто-то использует Alamofire, то это лучшее решение.

  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
источник
0

Это работает для меня: после setcookies добавьте 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

При добавлении нескольких элементов cookie вы можете сделать это следующим образом: ( path& domainтребуется для каждого элемента)

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

в противном случае будет установлен только первый элемент cookie.

YanXing Ou
источник
0

Вы также можете использовать WKWebsiteDataStore для получения аналогичного поведения HTTPCookieStorage из UIWebView.

let dataStore = WKWebsiteDataStore.default()
let cookies = HTTPCookieStorage.shared.cookies ?? [HTTPCookie]()
cookies.forEach({
    dataStore.httpCookieStore.setCookie($0, completionHandler: nil)
})
Tigu
источник
0

Код ниже хорошо работает в моем проекте Swift5. попробуйте загрузить URL-адрес с помощью WKWebView ниже:

    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
источник
0

Это мое решение для обработки файлов cookie и WKWebView в iOS 9 или новее.

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

Эта ошибка, которую я делал, заключалась в том, что я передавал весь URL-адрес в атрибуте домена, это должно быть только имя домена.

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

webView.configuration.websiteDataStore.httpCookieStore.setCookie(cookie)
Мухаммад Аамир Али
источник