我可以设置WKWebView使用的cookie吗?


135

我正在尝试将现有应用程序从切换UIWebViewWKWebView。当前的应用在之外管理用户的登录/会话,webview并将cookies身份验证所需的设置为NSHTTPCookieStore。不幸的是new WKWebView并没有使用cookiesfrom中的NSHTTPCookieStorage。还有另一种方法可以做到这一点吗?

Answers:


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及更低版本的旧答案

如果您需要在初始加载请求上设置cookie,则可以在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,则可以通过在文档开始时使用javascript以编程方式设置值来简单地使用WKUserScript来实现,如下所示:

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。如果您需要一些更高级的cookie,则可以在Mozilla的页面上找到有关cookie javascript API的更多信息。

是的,令人遗憾的是,Apple不支持UIWebView的许多优点。不知道他们是否会支持他们,但希望他们会很快做到这一点。希望这可以帮助!


1
在哪里为后续请求注入cookie的最佳位置是哪里?例如,上面的答案涵盖了初始页面加载,但是如果页面上的链接也指向相同的域,并且还需要将相同的Cookie注入请求中,该怎么办?didStartProvisionalNavigation?
Mason G. Zhwiti 2015年

1
抱歉,它不适用于您。在我看来,只要域相同,就不会有任何问题。您能否再次检查链接指向您加载请求所在域的代码?同样,Cookie也可以约束到特定的“路径”。也许这会引起一些问题?
mattr,2015年

11
请注意,设置Cookie的JavaScript技术不适用于“仅HTTP” cookie。
艾哈迈德·纳赛尔

1
上面的方法很好用...但是我可以在后续的AJAX调用中看到重复的cookie(仅重复一次)。
Durga Vundavalli

1
@ Axel92Dev的解决方法是确保从Web视图向服务器发出的第一个请求获得响应,该响应明确告诉Web视图使用HTTPOnly标志再次设置cookie(即:在响应中再次设置cookie)。您可以在初始化Webview时为此目的创建一个特殊的API,然后在成功时正常使用webview。
艾哈迈德·纳赛尔

63

在玩完这个答案之后(这很有帮助:),我们不得不进行一些更改:

  • 我们需要Web视图来处理多个域,而又不会泄漏这些域之间的私有Cookie信息
  • 我们需要它来兑现安全的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更改

我们还需要处理服务器更改cookie的值。这意味着添加另一个脚本以回调我们正在创建的Web视图以更新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的每个地方。现在,我们可以仅使用此代码作为帮助程序来创建我们的Web视图,并透明地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;
}

6
我已经将您的代码包装到WKWebView的子类中。随时查看github.com/haifengkao/YWebView
Hai Feng Kao

如果您的Cookie包含=符号,该怎么办?这行得通吗?
iOS

@iOSAddicted我这么认为。如果您的值是a=bcookie字符串,那么最终name=a=b;domain=.example.com;path=/-我相信标准会在key = value对中;第一个 拆分后再拆分=。我会测试一下:)
deanWombourne '17

您的回复对我有很大帮助,但是我想在您的帖子中添加一些内容,使用您的更新方法时会遇到一些风险,某些JS框架可能会创建名称相同但域不同的cookie,并且如果您尝试对其进行更新使用js方法,您很有可能会更新具有错误值的Cookie。同样对我们来说,由于我们的服务器在http和https之间进行了讨厌的重定向,因此必须除去js cookie字符串的安全标志,从而导致在某些讨厌的边缘情况下某些页面中不存在安全cookie。
RicardoDuarte '18

我实际上认为,我写这本书时所在的公司确实必须在发布后为其添加一些域保护。我们从未(afaik)遇到安全/不安全的问题-听起来像一场噩梦!
deanWombourne

42

必须先在配置上设置Cookie,然后再WKWebView创建。否则,即使使用WKHTTPCookieStoresetCookie完成处理程序,Cookie也不会可靠地同步到Web视图。这又从背部到这一行的文档WKWebViewConfiguration

@NSCopying var configuration: WKWebViewConfiguration { get }

@NSCopying有点深复制。该实现超出了我的范围,但是最终结果是,除非您在初始化Webview之前设置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
    }
}

上面的示例将视图创建推迟到最后一个可能的时刻,另一种解决方案是提前创建配置或Web视图,并在创建视图控制器之前处理异步特性。

最后要注意的一点是:创建此Web视图后,将其放到野外,如果不使用此答案中描述的方法,就无法添加更多Cookie 。但是,您可以使用WKHTTPCookieStoreObserverapi至少观察cookie发生的更改。因此,如果会话cookie在Webview中得到更新,则可以HTTPCookieStorage根据需要使用此新cookie 手动更新系统。

有关更多信息,请跳至此2017 WWDC会话“自定义Web内容加载”中的 18:00 。在本节的开始,有一个欺骗性的代码示例,它忽略了应该在完成处理程序中创建Web视图的事实。

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

18:00的现场演示澄清了这一点。

编辑至少从Mojave Beta 7和iOS 12 Beta 7开始,我看到Cookie的行为更加一致。该setCookie(_:)方法甚至似乎允许WKWebView在创建后设置Cookie 。我发现它的重要,虽然,以不碰processPool所有变量。Cookie设置功能在没有创建其他池时以及将该属性放置良好时效果最佳。我认为可以肯定地说,由于WebKit中的某些错误,我们遇到了问题。


在Mojave 10.14 beta 3和iOS 12 beta 3中,cookie处理/设置看起来更可靠
nteissler

6
非常深入和被低估的答案
尼古拉斯卡拉斯科

1
使用已加载的WKWebView的iOS 12仍然存在此问题。有时setCookie()实际上会立即与WKWebView同步,有时不会使处理有些零星
bmjohns

自从雷达被修复以来,我仍然看到过问题,但是频率降低了很多。您多久看到一次Cookie错误?如果您有一个可重现的,足够小的项目,那么我真的建议您在此处提交一个Webkit错误:webkit.org/reporting-bugs您也可以在Apple上推特布雷迪·艾德森Brady Eidson)(很高兴),他对这些类型的响应非常敏感报告和错误。
nteissler '18

这是正确的答案-无需手动将cookie转换为每个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)
    }
}

很棒的技巧,尤其是不拘一格的iOS如何在现有WKWebView中覆盖cookie。唯一的问题是以前的WKNavigationKey已过时。其他代码可能在旧代码上徒劳地等待着。
BaseZen

2
这样对吗?意识到它在某些情况下可能会起作用。但是,此委托方法(decisionPolicyForNavigationAction)的责任是决定策略;不实际加载请求。那是先前开始的。在那种情况下,这不会导致请求被加载两次吗?
Max MacLeod

2
@MaxMacLeod在这种else情况下,他使用调用decisionHandler闭包,.cancel因此webview实际上不会加载初始请求。在之后loadRequest被称为在else条件此委托方法将被再次调用该请求,它会进入if状态,因为Cookie头会存在。
halil_g

2
尽管在初始请求已经设置了一些cookie的else情况下这是行不通的,因为它永远不会进入条件。
halil_g

请注意,这是1)并非在每种情况下都适用-例如,在Web视图加载框架的情况下2)不安全-它将带有敏感信息的Cookie发送到第三方URL
Peter Prokop

20

这是我在Swift中的Mattrs解决方案版本,用于从HTTPCookieStorage注入所有cookie。这样做主要是为了注入身份验证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

这个工作对我罚款斯威夫特4(含有少量adjustements)
弗雷德里克·阿达

这对我来说非常有用,但是只有第二次访问该网站(第一次未设置cookie)时,有人遇到了吗?
MrChrisBarker '18年

第一次加载给错误,第二次加载工作:(可能是什么问题?
Shauket Sheikh

10

设置饼干

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

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

1
嗨,您可以添加代码以从中获取Cookie HTTPCookieStorage.shared吗?
markhorrocks

这是我让WKWebView将cookie添加到webview的每个请求的唯一方法
Chicowitz

如果响应中包含httponly cookie,则无法以这种方式获取cookie值。
大脑

1
这仅是将cookie设置回httpcookies存储区中,为wkwebview设置cookie的代码在哪里?
Shauket Sheikh 18/09/24

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嗯,在什么情况下不起作用?
Dan Loewenherz

我使用模拟器ios 8进行了测试,似乎没有发送Cookie。我已经仔细检查过了。
Shauket Sheikh,

我发布了我的答案,您可以尝试@Dan
Shauket Sheikh

7

在iOS 11中,您现在可以管理cookie了:),请参阅以下会话:https : //developer.apple.com/videos/play/wwdc2017/220/

在此处输入图片说明


2
@ShobhakarTiwari为什么?iOS11正式版中会发生任何变化吗?
Jacky's

如果仅支持iOS 11及更高版本,并且需要支持以前的版本,则最好的方法是在页面加载之前使用JavaScript。
PashaN

这对我有用,除了有时setcookie方法不会运行其完成处理程序,这意味着有时我的网页无法加载-仅发生在设备上,发生在第3/4/4/5次关闭并重新打开的事实webview,并且一旦发生,它就会一直发生,直到我重置应用程序为止-有人也遇到了这个问题吗?
Binya Koatz

5

发布此答案的原因是我尝试了许多解决方案,但没有一个人能正常工作,大多数答案在万一必须先设置Cookie且第一次未得到结果Cookie的情况下不起作用,请使用此解决方案iOS> = 11.0 <= iOS 11直到8.0,也可以首次使用Cookie同步。

对于iOS> = 11.0 -Swift 4.2

以这种方式获取http cookie并在wkwebview cookie存储中进行设置,将请求加载到wkwebview中是非常棘手的一点当cookie被完全设置时必须发送加载请求,这是我编写的功能。

调用带有关闭功能的函数可以在完成后调用load webview。仅供参考,此功能仅处理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

您需要设置一些额外的东西,您需要通过使用WKUserScript设置两个时间Cookie也不要忘记在请求中添加Cookie,否则您的Cookie不会第一次同步,并且您会看到页面无法正确地首次加载。这是我发现支持iOS 8.0 Cookie的难处

在创建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))


}

专注于此功能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
}

这是其他步骤wkuserscript不能立即同步cookie,有很多麻烦要用cookie加载第一次页面,如果终止进程,是要重新加载webview,但是我不建议使用它,这对用户的观点不好,无论您何时准备将请求集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答案进行修改和更新的。

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

我已经尝试了以上所有答案,但是都没有用。经过如此多的尝试,我终于找到了一种可靠的方式来设置WKWebview cookie。

首先,您必须创建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

与Web视图关联的进程池由其Web视图配置指定。在达到实现定义的过程限制之前,每个Web视图都将拥有自己的Web内容过程。之后,具有相同进程池的Web视图最终将共享Web Content进程。

如果您打算对子序列请求使用相同的WKWebview,请注意最后一句话

具有相同进程池的Web视图最终共享Web内容进程

我的意思是,如果您在每次为同一域配置WKWebView时都不使用WKProcessPool的同一实例(也许您的VC A包含WKWebView,并且您想在不同的地方创建VC A的不同实例),则可能存在冲突设置Cookie。为了解决该问题,在为加载域B的WKWebView首次创建WKProcessPool之后,我将其保存为单例并在每次必须创建加载相同域B的WKWebView时都使用相同的WKProcessPool

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:我从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)
   }
}

1

我对nteiss的回答。经过测试iOS 11, 12, 13。像你看起来没有用DispatchGroupiOS 13了。

我使用非静态函数includeCustomCookiesWKWebViewConfiguration,这样我可以更新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)
  }

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项目时,您可以按以下方式进行操作:(每个项目都必须pathdomain

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项目。


0

您还可以使用WKWebsiteDataStore从UIWebView获得与HTTPCookieStorage类似的行为。

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

0

下面的代码在我的项目Swift5中运行良好。尝试通过以下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)
    }

0

这是我在iOS 9或更高版本中使用Cookies和WKWebView处理的解决方案。

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

我正在做的这个错误是我在domain属性中传递了整个url,它应该仅仅是域名。

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

webView.configuration.websiteDataStore.httpCookieStore.setCookie(cookie)
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.