Posso configurar os cookies para serem usados ​​por um WKWebView?

135

Estou tentando mudar um aplicativo existente de UIWebViewpara WKWebView. O aplicativo atual gerencia o login / sessão do usuário fora do webviewe define o cookiesnecessário para autenticação no NSHTTPCookieStore. Infelizmente o novo WKWebViewnão usa o cookiesde NSHTTPCookieStorage. Existe outra maneira de conseguir isso?

Col
fonte

Respostas:

186

Editar apenas para iOS 11 ou superior

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

Como você está puxando-os do HTTPCookeStorage, você pode fazer isso:

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

Resposta antiga para iOS 10 e abaixo

Se você precisar que seus cookies sejam definidos na solicitação de carregamento inicial, será possível configurá-los em NSMutableURLRequest. Como os cookies são apenas um cabeçalho de solicitação especialmente formatado, isso pode ser alcançado da seguinte maneira:

WKWebView * webView = /*set up your webView*/
NSMutableURLRequest * request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"http://example.com/index.html"]];
[request addValue:@"TeskCookieKey1=TeskCookieValue1;TeskCookieKey2=TeskCookieValue2;" forHTTPHeaderField:@"Cookie"];
// use stringWithFormat: in the above line to inject your values programmatically
[webView loadRequest:request];

Se você solicitar que as solicitações AJAX subseqüentes na página tenham seus cookies definidos, isso pode ser alcançado simplesmente usando WKUserScript para definir os valores programaticamente via javascript no início do documento, da seguinte maneira:

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

A combinação dessas duas técnicas deve fornecer ferramentas suficientes para transferir valores de cookies do Native App Land para o Web View Land. Você pode encontrar mais informações na API de cookies javascript na página da Mozilla se precisar de alguns cookies mais avançados.

Sim, é péssimo que a Apple não suporte muitas das vantagens do UIWebView . Não tenho certeza se eles irão apoiá-los, mas espero que eles continuem com isso em breve. Espero que isto ajude!

Mattr
fonte
1
Onde é o melhor lugar para injetar cookies para solicitações subsequentes? Por exemplo, o carregamento inicial da página é abordado na resposta acima, mas e se houver links na página que também levem ao mesmo domínio e também precisem dos mesmos cookies injetados na solicitação? didStartProvisionalNavigation?
Mason G. Zhwiti
1
desculpe, não está funcionando para você. Na minha opinião, desde que os domínios sejam os mesmos, não haverá nenhum problema. Você pode verificar novamente o código em que o link está apontando para o mesmo domínio em que você carregou a solicitação? Além disso, os cookies também podem ser restritos a um "caminho" específico. Talvez isso esteja causando alguns problemas?
mattr 4/15
11
Observe que a técnica javascript para definir os cookies não funcionará para cookies "Somente HTTP".
Ahmed Nasser
1
O método acima funciona muito bem ... mas eu pude ver os cookies duplicados nas chamadas AJAX subsequentes (duplicadas apenas uma vez).
Durga Vundavalli
1
@ Axel92Dev, uma solução alternativa seria garantir que a primeira solicitação feita da sua visualização na web para o servidor receba uma resposta que diga explicitamente à webview para definir os cookies novamente com o sinalizador HTTPOnly (ou seja: defina os cookies novamente na resposta). Você pode criar uma API especial para esse único objetivo ao inicializar a visualização na web e usá-la normalmente com êxito.
Ahmed Nasser
64

Depois de jogar com esta resposta (que foi extraordinariamente útil :), tivemos que fazer algumas alterações:

  • Precisamos de visualizações da web para lidar com vários domínios sem vazar informações de cookies particulares entre esses domínios
  • Precisamos honrar cookies seguros
  • Se o servidor alterar um valor de cookie, queremos que nosso aplicativo saiba sobre ele em NSHTTPCookieStorage
  • Se o servidor alterar um valor de cookie, não queremos que nossos scripts o redefinam para seu valor original quando você segue um link / AJAX etc.

Então, modificamos nosso código para ser esse;

Criando uma solicitação

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

Isso garante que a primeira solicitação tenha os cookies corretos definidos, sem enviar cookies do armazenamento compartilhado para outros domínios e sem enviar cookies seguros para uma solicitação não segura.

Lidar com pedidos adicionais

Também precisamos garantir que outras solicitações tenham os cookies definidos. Isso é feito usando um script que é executado em carga documento que verifica para ver se há um conjunto de cookies e se não, defina-o para o valor 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];

Lidando com alterações de cookies

Também precisamos lidar com o servidor alterando o valor de um cookie. Isso significa adicionar outro script para retornar à visualização na web que estamos criando para atualizar nossa NSHTTPCookieStorage.

WKUserScript *cookieOutScript = [[WKUserScript alloc] initWithSource:@"window.webkit.messageHandlers.updateCookies.postMessage(document.cookie);"
                                                       injectionTime:WKUserScriptInjectionTimeAtDocumentStart
                                                    forMainFrameOnly:NO];
[userContentController addUserScript:cookieOutScript];

[userContentController addScriptMessageHandler:webView
                                          name:@"updateCookies"];

e implementando o método delegate para atualizar os cookies que foram alterados, certificando-se de que estamos atualizando apenas os cookies do domínio atual!

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

Isso parece resolver nossos problemas de cookies sem precisar lidar com cada local em que usamos o WKWebView de maneira diferente. Agora, podemos apenas usar esse código como um auxiliar para criar nossas visualizações da Web e ele é atualizado de forma transparente NSHTTPCookieStorage.


Edição: Acontece que eu usei uma categoria privada no NSHTTPCookie - aqui está o código:

- (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
fonte
6
Eu agrupei seu código em uma subclasse do WKWebView. Sinta-se livre para conferir github.com/haifengkao/YWebView
Hai Feng Kao
E se seus cookies contiverem sinais = no valor? Isso funcionaria?
iOSAddicted
@iOSAddicted Acho que sim. Se o seu valor fosse, a=bvocê terminaria com a sequência de cookies name=a=b;domain=.example.com;path=/- acredito que o padrão se divide ;e depois se divide no primeiro = no par chave = valor. No entanto, eu testaria isso :) :)
deanWombourne #
sua resposta me ajudou muito, no entanto, gostaria de adicionar algo à sua postagem, há vários riscos ao usar o método de atualização, algumas estruturas JS podem criar cookies com o mesmo nome, mas com domínio diferente, e se você tentar atualizá-lo usando os métodos js, você tem um alto risco de atualizar um cookie com um valor errado. Também para nós, a cadeia de cookies js, teve que ser removida de seu sinalizador seguro, pois nosso servidor faz redirecionamentos desagradáveis ​​entre http e https, fazendo com que cookies seguros não estejam presentes em algumas páginas em alguns casos extremos desagradáveis.
RicardoDuarte
Na verdade, acho que a empresa em que estava quando escrevi isso precisou adicionar alguma proteção de domínio depois que foi lançada. Nós (afaik) nunca enfrentamos o problema de segurança / insegurança - parece um pesadelo!
deanWombourne
42

Os cookies devem ser definidos na configuração antes da WKWebViewcriação. Caso contrário, mesmo com WKHTTPCookieStoreo setCookiemanipulador de conclusão, os cookies não serão sincronizados de maneira confiável com a visualização da web. Isso remonta a essa linha nos documentos emWKWebViewConfiguration

@NSCopying var configuration: WKWebViewConfiguration { get }

Isso @NSCopyingé uma cópia profunda. A implementação está além de mim, mas o resultado final é que, a menos que você defina cookies antes de inicializar a visualização na web, não poderá contar com a presença dos cookies. Isso pode complicar a arquitetura do aplicativo, pois a inicialização de uma exibição se torna um processo assíncrono. Você vai acabar com algo assim

extension WKWebViewConfiguration {
    /// Async Factory method to acquire WKWebViewConfigurations packaged with system cookies
    static func cookiesIncluded(completion: @escaping (WKWebViewConfiguration?) -> Void) {
        let config = WKWebViewConfiguration()
        guard let cookies = HTTPCookieStorage.shared.cookies else {
            completion(config)
            return
        }
        // Use nonPersistent() or default() depending on if you want cookies persisted to disk
        // and shared between WKWebViews of the same app (default), or not persisted and not shared
        // across WKWebViews in the same app.
        let dataStore = WKWebsiteDataStore.nonPersistent()
        let waitGroup = DispatchGroup()
        for cookie in cookies {
            waitGroup.enter()
            dataStore.httpCookieStore.setCookie(cookie) { waitGroup.leave() }
        }
        waitGroup.notify(queue: DispatchQueue.main) {
            config.websiteDataStore = dataStore
            completion(config)
        }
    }
}

e então usar algo como

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

O exemplo acima adia a criação de visualizações até o último momento possível; outra solução seria criar a configuração ou a visualização na Web com bastante antecedência e lidar com a natureza assíncrona antes da criação de um controlador de exibição.

Uma observação final: depois de criar essa visualização na Web, você a soltou na natureza, não poderá adicionar mais cookies sem usar os métodos descritos nesta resposta . No entanto, você pode usar a WKHTTPCookieStoreObserverAPI para observar pelo menos as alterações ocorridas nos cookies. Portanto, se um cookie de sessão for atualizado na visualização da web, você poderá atualizar manualmente o sistema HTTPCookieStoragecom esse novo cookie, se desejar.

Para saber mais, pule para as 18:00 neste carregamento personalizado de conteúdo da web da sessão da WWDC de 2017 . No início desta sessão, há um exemplo de código enganoso que omite o fato de que a visualização da web deve ser criada no manipulador de conclusão.

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

A demonstração ao vivo às 18:00 esclarece isso.

Editar No Mojave Beta 7 e iOS 12 Beta 7, pelo menos, estou vendo um comportamento muito mais consistente com os cookies. O setCookie(_:)método até parece permitir a configuração de cookies após a WKWebViewcriação. Mas achei importante não tocar na processPoolvariável. A funcionalidade de configuração de cookie funciona melhor quando nenhum pool adicional é criado e quando essa propriedade é deixada em paz. Eu acho que é seguro dizer que estávamos tendo problemas devido a alguns erros no WebKit.

nteissler
fonte
Looks como o processamento de cookies / configuração é mais confiável em Mojave 10,14 beta 3 e iOS 12 beta 3
nteissler
6
Muito em profundidade e resposta subestimado
Nicolás Carrasco
1
Ainda estou tendo esse problema no iOS 12 com um WKWebView já carregado. Às vezes setCookie () vai realmente ser sincronizado com o WKWebView de imediato, às vezes ele não vai fazer a manipulação pouco esporádico
bmjohns
Também vi problemas também desde que o radar foi corrigido, mas com muito menos frequência. Com que frequência você vê a falha do cookie? Se você tem um projeto reproduzível e pequeno o suficiente, eu realmente recomendo enviar um bug do webkit aqui: webkit.org/reporting-bugs Você também pode twittar Brady Eidson (agradavelmente) um arquiteto de webkit da apple que responde muito bem a esses tipos de relatórios e bugs.
Nteissler
esta é a resposta correta - não é necessário traduzir manualmente os cookies como campos de cabeçalho em cada URLRequest, é apenas que setCookie () precisa ser usado conforme descrito aqui.
Guillaume Laurent
25

trabalhe para mim

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
fonte
Hack incrível, especialmente como o iOS intransigente é sobre substituir um cookie em um WKWebView existente. O único problema é que o WKNavigationKey anterior ficou obsoleto. Outro código pode estar esperando em vão no antigo.
BaseZen 02/04
2
isto está correto? Aprecie que pode funcionar em algumas circunstâncias. No entanto, a responsabilidade desse método delegado - decidePolicyForNavigationAction - é decidir a política; para não carregar realmente a solicitação. Isso foi iniciado anteriormente. Nesse caso, isso não faz com que a solicitação seja carregada duas vezes?
Max MacLeod
2
@MaxMacLeod Na elsecondição em que ele está chamando o decisionHandlerfechamento, .cancelpara que webviewele não carregue a solicitação inicial. Depois de loadRequestchamado na elsecondição, esse método delegado será chamado novamente para essa solicitação e entrará na ifcondição porque o Cookiecabeçalho estará presente.
#
2
Embora isso não funcione quando a solicitação inicial já tiver alguns cookies definidos, ela nunca entrará na elsecondição.
#
Note-se que este é 1) não funciona para todas as situações - por exemplo, caso quando as cargas quadros vista web 2) Não seguro - poderia enviar cookie com informações sensíveis ao 3º URL partido
Peter Prokop
20

Aqui está minha versão da solução Mattrs no Swift para injetar todos os cookies do HTTPCookieStorage. Isso foi feito principalmente para injetar um cookie de autenticação para criar uma sessão do usuário.

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
fonte
melhor adicionar esta linha para garantir que a formatação local está correto:dateFormatter.locale = NSLocale(localeIdentifier: "en_US_POSIX")
bubuxu
onde chamar isso?
Markhorrocks
Esta multa trabalhou para mim em Swift 4 (com pequenas alterações)
Frédéric Adda
Isso funciona muito bem para mim, mas apenas na segunda vez que visito o site (pela primeira vez os cookies não estão definidos) - alguém se deparou com isso?
MrChrisBarker
Primeiro carregamento dá erro trabalho de segundo carregamento :( qual poderia ser o problema? #
Shauket Sheikh
10

definir cookie

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

excluir cookie

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

Atualização 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
fonte
1
Olá, você também pode adicionar código para obter cookies HTTPCookieStorage.shared?
markhorrocks
Esta é a única maneira que eu tenho WKWebView para adicionar os cookies a cada solicitação feita pela webview
Chicowitz
Se ele contiver um cookie de reposição em resposta, você não poderá obter o valor dos cookies dessa maneira.
cérebro
1
isso está apenas configurando cookies de volta ao armazenamento httpcookies. Onde está o código que configura os cookies para o wkwebview?
Shauket Sheikh 24/09
8

Depois de analisar várias respostas aqui e não ter sucesso, vasculhei a documentação do WebKit e deparei-me com o requestHeaderFieldsmétodo estático HTTPCookie, que converte uma matriz de cookies em um formato adequado para um campo de cabeçalho. Combinar isso com o insight do mattr de atualizar o URLRequestantes de carregá-lo com os cabeçalhos dos cookies me levou até a linha de chegada.

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)

Para tornar isso ainda mais simples, use uma extensão:

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

Agora, apenas se torna:

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

Esta extensão também está disponível no LionheartExtensions se você deseja apenas uma solução drop-in. Felicidades!

Dan Loewenherz
fonte
1
@ShauketSheikh hmm, em que situações isso não funciona?
Dan Loewenherz
Eu testei usando o simulador ios 8, parece não estar enviando cookies. Eu verifiquei duas vezes.
Shauket Sheikh 25/09
eu postei minha resposta você pode tentar @ Dan
Shauket Sheikh
7

No iOS 11, você pode gerenciar o cookie agora :), consulte esta sessão: https://developer.apple.com/videos/play/wwdc2017/220/

insira a descrição da imagem aqui

Jacky
fonte
2
@ShobhakarTiwari why? acontece alguma alteração na versão formal do iOS11?
Jacky
O melhor caminho a percorrer se você suportar apenas o iOS 11 e superior, se precisar suportar versões anteriores, use JavaScript antes do carregamento da página.
Pashan
Isso funciona para mim, exceto pelo fato de que, às vezes, o método setcookie NÃO executa seu manipulador de conclusão, o que significa que às vezes minha página da web não carrega - só acontece no dispositivo, acontece na 3ª / 4ª / 5ª vez fechando e reabrindo o webview e, depois que acontece uma vez, continua acontecendo até eu redefinir o aplicativo - alguém também se depara com isso?
Binya Koatz
5

A razão por trás da postagem desta resposta é que eu tentei muitas soluções, mas ninguém funciona corretamente, a maioria das respostas não funciona no caso em que é necessário definir o cookie pela primeira vez e o resultado obtido não é sincronizado pela primeira vez. Use esta solução para ambos iOS> = 11.0 <= iOS 11 até 8.0, também funciona com sincronização de cookies pela primeira vez.

Para iOS> = 11.0 - Swift 4.2

Obtenha cookies http e defina na loja de cookies wkwebview dessa maneira, é um ponto muito complicado para carregar sua solicitação no wkwebview , deve-se enviar uma solicitação para carregar quando os cookies serão definidos completamente, aqui está a função que eu escrevi.

Chamar a função com o fechamento concluído, você chama carregar a webview. Para sua informação, esta função suporta apenas iOS> = 11.0

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

Aqui está a implementação da função 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()
}
}

Para iOS 8 até iOS 11

você precisa configurar algumas coisas extras para definir dois cookies únicos, usando o WKUserScript e também não se esqueça de adicionar cookies no pedido, caso contrário, o cookie não será sincronizado pela primeira vez e você verá que a página não será carregada corretamente pela primeira vez. este é o diabo que eu encontrei para suportar cookies para iOS 8.0

antes de criar o objeto 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))


}

Concentre-se nessa função 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
}

Aqui está outra etapa em que o wkuserscript não sincroniza os cookies imediatamente; há muitas coisas para carregar a primeira página com o cookie 1: recarregar a visualização na web novamente se ela terminar o processo, mas eu não recomendo usá-la, isso não é bom para o ponto de vista do usuário , heck, sempre que você estiver pronto para carregar os cookies do conjunto de solicitações no cabeçalho da solicitação, assim, não esqueça de adicionar a verificação de versão do iOS. antes da solicitação de carregamento, chame esta função.

request?.addCookies()

eu escrevi extensão para 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

        }
    }

  }
}

agora você está pronto para testar o iOS> 8

Shauket Sheikh
fonte
2

Encontre a solução que provavelmente funcionará para você imediatamente. Basicamente, é modificado e atualizado para a resposta do Swift 4 @ user3589213 .

func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
    let headerKeys = navigationAction.request.allHTTPHeaderFields?.keys
    let hasCookies = headerKeys?.contains("Cookie") ?? false

    if hasCookies {
        decisionHandler(.allow)
    } else {
        let cookies = HTTPCookie.requestHeaderFields(with: HTTPCookieStorage.shared.cookies ?? [])

        var headers = navigationAction.request.allHTTPHeaderFields ?? [:]
        headers += cookies

        var req = navigationAction.request
        req.allHTTPHeaderFields = headers

        webView.load(req)

        decisionHandler(.cancel)
    }
}
Vadim Bulavin
fonte
1

Eu tentei todas as respostas acima, mas nenhuma delas funciona. Após tantas tentativas, finalmente encontrei uma maneira confiável de definir o cookie WKWebview.

Primeiro, você deve criar uma instância do WKProcessPool e configurá-la como WKWebViewConfiguration que será usada para inicializar o próprio WkWebview:

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

A configuração do WKProcessPool é a etapa mais importante aqui. O WKWebview faz uso do isolamento do processo - o que significa que ele é executado em um processo diferente do processo do seu aplicativo. Às vezes, isso pode causar conflitos e impedir que seu cookie seja sincronizado corretamente com o WKWebview.

Agora, vejamos a definição de WKProcessPool

O conjunto de processos associado a uma visualização na web é especificado por sua configuração de visualização na web. Cada visualização da Web recebe seu próprio processo de Conteúdo da Web até que um limite de processo definido pela implementação seja atingido; depois disso, as exibições da Web com o mesmo pool de processos acabam compartilhando os processos de Conteúdo da Web.

Preste atenção na última frase se você planeja usar o mesmo WKWebview para solicitações subsequentes

visualizações da web com o mesmo conjunto de processos acabam compartilhando processos de conteúdo da web

o que quero dizer é que, se você não usar a mesma instância do WKProcessPool cada vez que configurar um WKWebView para o mesmo domínio (talvez você tenha um VC A que contém um WKWebView e deseje criar instâncias diferentes do VC A em locais diferentes ), pode haver cookies de configuração de conflito. Para resolver o problema, após a primeira criação do WKProcessPool para um WKWebView que carrega o domínio B, eu o salvo em um singleton e uso o mesmo WKProcessPool toda vez que tenho que criar um WKWebView que carrega o mesmo domínio 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
}()

Após o processo de inicialização, você pode carregar um URLRequest dentro do bloco de conclusão de httpCookieStore.setCookie. Aqui, você deve anexar o cookie ao cabeçalho da solicitação, caso contrário não funcionará.

P / s: roubei a extensão da fantástica resposta acima de 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
fonte
1

Minha versão da resposta de nteiss. Testado em iOS 11, 12, 13. Parece que você não tem que usar DispatchGroupem iOS 13mais.

Eu uso função non-static includeCustomCookieson WKWebViewConfiguration, para que eu possa atualizar cookiescada vez que eu criar novos 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()
        }
    }
}

Então eu uso assim:

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

A melhor correção para solicitações XHR é mostrada aqui

Versão 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
fonte
0

Se alguém estiver usando o Alamofire, essa é a melhor solução.

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

Isso funciona para mim: Após setcookies, adicione 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
fonte
0

Ao adicionar itens de cookie multiplicados, você pode fazer o seguinte: ( pathe domainé necessário para cada 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];

caso contrário, apenas o primeiro item do cookie será definido.

YanXing Ou
fonte
0

Você também pode usar o WKWebsiteDataStore para obter um comportamento semelhante ao HTTPCookieStorage no UIWebView.

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

O código abaixo funciona bem no meu projeto Swift5. tente carregar o URL pelo WKWebView abaixo:

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

Esta é a minha solução para lidar com cookies e WKWebView no iOS 9 ou posterior.

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

Esse erro que eu estava cometendo era passar o URL inteiro no atributo de domínio, que deveria ser apenas o nome de domínio.

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

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