Могу ли я установить куки для использования WKWebView?
Я пытаюсь переключить существующее приложение из UIWebView в WKWebView. Текущее приложение управляет логином / сеансом пользователей за пределами веб-представления и устанавливает файлы cookie, необходимые для аутентификации, в хранилище NSHTTPCookieStore. К сожалению, новый WKWebView не использует куки из NSHTTPCookieStorage. Есть ли другой способ добиться этого?
13 ответов:
редактировать только для iOS 11+
использовать WKHTTCookieStore:
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. Поскольку cookies - это просто специально отформатированный заголовок запроса, это может быть достигнуто, например Итак:
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 на странице имели свои куки-файлы, это может быть достигнуто простым использованием 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 из родной земли приложения в веб-вид Земли. Вы можете найти дополнительную информацию о cookie javascript api на странице mozilla, если вам нужны некоторые более продвинутые куки.
Да это отстой, что Apple не поддерживает многие из тонкости UIWebView. Не уверен, что они когда-нибудь поддержат их, но, надеюсь, они скоро это сделают. Надеюсь, это поможет!
после игры с ответ (что было фантастически полезно :) нам пришлось внести несколько изменений:
- нам нужны веб-представления для работы с несколькими доменами без утечки личной информации 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, а если нет, установите его в значение
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
нам также нужно иметь дело с сервером, изменяющим значение файла 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
для нас.
EDIT: оказывается, я использовал приватную категорию на 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; }
работай на меня
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) } }
вот моя версия 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 }
куки-файлы должны быть установлены в конфигурации перед это. В противном случае, даже с
WKHTTPCookieStore
' ssetCookie
обработчик завершения, куки-файлы не будут надежно синхронизированы с веб-представлением. Это восходит к этой строке из docs onWKWebViewConfiguration
@NSCopying var configuration: WKWebViewConfiguration { get }
это
@NSCopying
очень глубокая копия. Реализация находится за пределами меня, но конечный результат заключается в том, что если вы не установите куки перед инициализацией webview, вы не можете рассчитывать на печенье там. Это может усложнить архитектуру приложения, поскольку не инициализация представления становится асинхронным процессом. Вы будете в конечном итоге с чем-то вроде этого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 } }
приведенный выше пример откладывает создание представления до последнего возможного момента, другим решением было бы создать конфигурацию или webview заблаговременно и обработать асинхронный характер перед созданием представления контроллер.
последнее примечание: после того, как вы создадите этот webview, вы выпустили его в дикую природу, вы не можете добавить больше файлов cookie без использования методов, описанных в ответ. Однако вы можете использовать
WKHTTPCookieStoreObserver
API, чтобы, по крайней мере, наблюдать изменения, происходящие с печеньем. Поэтому, если файл cookie сеанса обновляется в webview, вы можете вручную обновить системуHTTPCookieStorage
С этим новым cookie, если это необходимо.для получения дополнительной информации, перейдите к 18: 00 на этом 2017 Сеанс WWDC загрузка пользовательского веб-контента. В начале этого сеанса есть обманчивый пример кода, который опускает тот факт, что webview должен быть создан в обработчике завершения.
cookieStore.setCookie(cookie!) { webView.load(loggedInURLRequest) }
живая демонстрация в 18: 00 проясняет это.
Edit по крайней мере, в Mojave Beta 7 и iOS 12 Beta 7 я вижу гораздо более последовательное поведение с файлами cookie. Элемент
setCookie(_:)
метод даже позволяет устанавливать куки послеWKWebView
есть был создан. Я действительно нашел это важным, хотя, чтобы не трогать theprocessPool
переменная вообще. Функция настройки cookie работает лучше всего, когда дополнительные пулы не создаются и когда это свойство оставлено в покое. Я думаю, можно с уверенностью сказать, у нас были проблемы из-за ошибки в WebKit.
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) } }
установить 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() }
в iOS 11 Теперь вы можете управлять cookie:), см. этот сеанс:https://developer.apple.com/videos/play/wwdc2017/220/
пожалуйста, найдите решение, которое, скорее всего, будет работать для вас из коробки. В основном он изменен и обновлен для 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) } }
после просмотра различных ответов здесь и не имея никакого успеха, я прочесал документацию WebKit и наткнулся на
requestHeaderFields
статический метод наHTTPCookie
, который преобразует массив в cookie-файлов в формат, подходящий для поля заголовка. Сочетая это с mattr озарения обновленияURLRequest
перед загрузкой его с заголовками cookie получил меня через финишную черту.Swift 4.1:
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 если вы просто хотите падение в решении. Ура!
причина опубликованного ответа заключается в том, что я пробовал много решений, но не работал должным образом большая часть ответа не работает в первый раз, пожалуйста, используйте мое решение, оно работает как для iOS больше 11.0, так и меньше iOS 11 до 8.0
для iOS >= 11.0 -- Swift 4.2
Get http cookies и в wkwebview куки-магазин, как этот путь, это очень сложный момент, чтобы загрузить ваш запрос в wkwebview, необходимо отправить запрос загрузка, когда куки будут установлены полностью, вот функция, которую я написал.
вызов функции с закрытие в завершении вы вызываете load webview. К вашему сведению эта функция обрабатывает только iOS >= 11.0
self.WwebView.syncCookies { if let request = self.request { self.WwebView.load(request) } }
вот реализация для syncCookies
лучшее исправление для запросов 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) }