0e6d0efaaf43618a8810e97201d02fe1
WKWebView 的 Cookie 坑,我已填好

前言

如果开发者一开始选择 UIWebView 作为 h5 的容器,开发者无须关注 cookie 的存取问题,因为后台下发的cookie会自动存储到NSHTTPCookieStorage这个容器中,webView内部的请求会自动从NSHTTPCookieStorage获取合适的cookie带上去。

但是苹果爸爸说UIWebView在iOS12之后是一个deprecated接口,希望开发者迁移到WKWebView中。但是WKWebView关于cookie的存取却不像UIWebView那么方便,主要是因为整个框架的改变,WKWebView已经不单单是在app这一侧,WKWebView容器发起的h5请求已不在app进程中发起和响应处理,而是在专门的web进程中处理,所以WKWebView的网络请求无法直接从NSHTTPCookieStorage取到cookie,所以当h5访问一些带鉴权的接口就会出现问题。

下面我提供个case给大家解析cookie机制在新容器是如何运转和提供正确的设置姿势。

案例

首先我们先在我们的代码打个log,将后台下发的cookie给打印出来,也验证下后台是否下发成功了:

通过控制台log,发现后台下发的cookie已经同步到NSHTTPCookieStorage,我们打印出cookie的key和value:

接着我启动我们的WKWebView容器发起第一个请求:

同时我们启动Charles进行网络抓包,抓取webview中的网络请求:

图中那个请求就是webview容器的第一个请求,它会进行鉴权相关的判断,通过查看这个请求的请求体发现请求体里没有cookie字段,说明这个请求没有带上cookie。对应的业务展示也变成系统异常:

接下开启Safari的开发调试模式,调试当前这个h5界面,我们发现后台下发的cookie已经同步到WKWebView侧,这是因为WKWebView内也有cookie的容器,而且每隔一段时间就和app侧NSHTTPCookieStorage进行同步,而且这个同步是进程级别的同步,而且这个同步是单向,这个后面会进行解析。为什么第一次请求没有带上cookie是因为app侧NSHTTPCookieStorage的cookie还没同步给WKWebView的cookieStorage,导致网络请求没有带上cookie,当我们开启Safari的开发调试模式的时候已经完成同步,所以我们可以看到对应WKWebView的cookieStorage存的cookie跟app控制台log的cookie一样:

如果这时候我们点击Safari调试器的刷新按钮,响应会是业务正常,因为刷新重新发起请求,这时候请求已经带上cookie

设置cookie的正确姿势

在iOS11之后,苹果爸爸终于理解开发者关于设置cookie的痛楚,所以开放一个接口给我们设置WKWebView的cookieStorage,要注意这个接口是异步的,所以我们需要等待WKWebView异步设置cookie完成后才发起请求:

     @wb_weakify(self)
        if (@available(iOS 11.0, *)) {
            [self.webview.configuration.websiteDataStore.httpCookieStore setCookie:eventCK completionHandler:^{
                @wb_strongify(self)
                NSURLRequest *request = [NSURLRequest requestWithURL:url];
                [self.webview loadRequest:request];
             }];

为了适配iOS11以前的系统版本,设置cookie就没有那么直接,我们查阅网上很多文章,甚至是一些比较权威的团队发出来的文章,都是以下这种思路:通过key-Value构造一个cookie,WKWebView loadRequest 前,在 request header 中设置 Cookie, 解决首个请求 Cookie 带不上的问题,通过 document.cookie 设置 Cookie 解决后续页面(同域)Ajax、iframe 请求的 Cookie 问题:

    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    [request setValue:[NSString stringWithFormat:@"%@;",[self _getCookieString:eventCK]] forHTTPHeaderField:@"Cookie"];
    WKUserScript * cookieScript = [[WKUserScript alloc] initWithSource:
    [NSString stringWithFormat:@"document.cookie = '%@';",[self _getCookieString:eventCK]]
                                                                 injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:NO];
    [_webview.configuration.userContentController addUserScript: cookieScript];
    [_webview loadRequest:request];
    - (NSString *)_getCookieString:(NSHTTPCookie *)cookie {
        NSString *string = [NSString stringWithFormat:@"%@=%@;",
                        cookie.name,
                        cookie.value];
        return string;
}

按着这些文章改造我们的代码还是没有解决业务鉴权失败的问题,因为这些文章都忽律一个关键的操作,就是cookie的构建,很多文章只是简单构造key-Value,导致因为同源策略请求带不上cookie。cookie有四个关键的标识:value(键值对)、expires(过期日期)、domain(域)、path(路径),如果有一个标识不一样,它就是一个新的cookie,在iOS中如果cookie只指定value,其他会设置为默认值,我们通过Safari调试器捕获这个cookie:

我们可以看到WKWebView的cookieStorage存在两个同名同值的cookie,但是他们的域、路径、过期时间都不同,我们可以看出只设置key-Value的cookie默认的域就是发起的请求的url的域名,对应的路径也是发起请求的url的path,第二个cookie是正确的但他是后面同步过来的,第一个cookie也就是我们通过key-Value构建的cookie,请求带不上去这个cookie,因为它与业务请求不同源(业务请求只支持域为".webank.com"和path为"/"的cookie)。所以正确的姿势应该是构造一个全cookie:
```

  • (NSString *)_getCookieString:(NSHTTPCookie *)cookie {

    NSString *string = [NSString stringWithFormat:@"%@=%@;domain=%@;expiresDate=%@;path=%@;sessionOnly=%@;isSecure=%@",
    cookie.name,
    cookie.value,
    cookie.domain,

top Created with Sketch.