美文网首页
WKWebView的cookie问题

WKWebView的cookie问题

作者: hang_a7bf | 来源:发表于2020-03-09 13:58 被阅读0次

    以下内容转载http://xiaozhuanlan.com/topic/2517460839 终端杂谈的一篇文章

    前言

    如果开发者一开始选择 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(iOS11.0, *)) {

    [self.webview.configuration.websiteDataStore.httpCookieStoresetCookie:eventCKcompletionHandler:^{

    @wb_strongify(self)

    NSURLRequest *request = [NSURLRequestrequestWithURL:url];

    [self.webviewloadRequest:request];

                }];

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

    NSMutableURLRequest*request = [NSMutableURLRequestrequestWithURL:url];

    [request setValue:[NSStringstringWithFormat:@"%@;",[self_getCookieString:eventCK]] forHTTPHeaderField:@"Cookie"];

    WKUserScript* cookieScript = [[WKUserScriptalloc] initWithSource:

    [NSStringstringWithFormat:@"document.cookie = '%@';",[self_getCookieString:eventCK]]

    injectionTime:WKUserScriptInjectionTimeAtDocumentStartforMainFrameOnly:NO];

        [_webview.configuration.userContentController addUserScript: cookieScript];

        [_webview loadRequest:request];

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

    NSString*string = [NSStringstringWithFormat:@"%@=%@;",

                            cookie.name,

                            cookie.value];

    returnstring;

    }

    按着这些文章改造我们的代码还是没有解决业务鉴权失败的问题,因为这些文章都忽律一个关键的操作,就是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 = [NSStringstringWithFormat:@"%@=%@;domain=%@;expiresDate=%@;path=%@;sessionOnly=%@;isSecure=%@",

                            cookie.name,

                            cookie.value,

                            cookie.domain,

                            cookie.expiresDate,

    cookie.path ?:@"/",

    cookie.isSecure ?@"TRUE":@"FALSE",

    cookie.sessionOnly ?@"TRUE":@"FALSE"];

    returnstring;

    }

    经过这样设置之后,业务请求终于能够带上正确的cookie:

    CookieStorage同步是单向的?

    想必很多基于WKWebView开发的同学都看过bugly的这篇文章《WKWebView 那些坑》,里面提到Cookie那一块内容讲到“ WKWebView 实例其实也会将 Cookie 存储于 NSHTTPCookieStorage 中,但存储时机有延迟,在iOS 8上,当页面跳转的时候,当前页面的 Cookie 会写入 NSHTTPCookieStorage 中,而在 iOS 10 上,JS 执行 document.cookie 或服务器 set-cookie 注入的 Cookie 会很快同步到 NSHTTPCookieStorage 中”。其实这种说法讲的不够准确,只有最后一话的后半句是正确,正确的说法是cookie是同步是单向,且只有app侧的NSHTTPCookieStorage将cookie同步到WKWebView维护的cookieStorage,WKWebView的cookie无法同步到NSHTTPCookieStorage中,本着对读者负责的态度,我进行实验进行验证,验证代码如下,验证步骤已经注释在代码中:

    //WKWebView请求带上cookie

    WKUserScript* cookieScript = [[WKUserScriptalloc] initWithSource:

    [NSStringstringWithFormat:@"document.cookie = '%@';",[self_getCookieString:eventCK]]

    injectionTime:WKUserScriptInjectionTimeAtDocumentStartforMainFrameOnly:NO];

                [_webview.configuration.userContentController addUserScript: cookieScript];

                [_webview loadRequest:request];

    //一般cookie同步应该在很短时间内完成,我们假定一个较长的时间10s,10s后从NSHTTPCookieStorage获取所有cookie,验证WKWebView的cookie有没有同步到这里

    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10*NSEC_PER_SEC)), dispatch_get_main_queue(), ^{

    NSArray*cookies = [NSHTTPCookieStoragesharedHTTPCookieStorage].cookies;

    for(NSHTTPCookie*ckincookies) {

    NSLog(@"app cookies : [%@:%@]",ck.name,ck.value);

                    }

                });

    //生成测试cookie,key为testWKCK,为了让请求带上cookie,cookie的domain和path设置为符合业务要求的值

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

    NSString*string = [NSStringstringWithFormat:@"%@=%@;domain=%@;expiresDate=%@;path=%@;sessionOnly=%@;isSecure=%@",

    @"testWKCK",

    @"111111111",

                            cookie.domain,

                            cookie.expiresDate,

    cookie.path ?:@"/",

    cookie.isSecure ?@"TRUE":@"FALSE",

    cookie.sessionOnly ?@"TRUE":@"FALSE"];

    returnstring;

    }

    结果验证:

    通过Sarafi调试器我们可以看到我们WKWebView存在一个名为testWKCK的cookie,我们弄个10秒的定时器,10秒后打印app侧所有cookie,发现只有后台下发的那个cookie,名为testWKCK的cookie在app侧没有保存,这说明了cookie的同步是单向,只有从app侧到webThread侧。

    既然是单向同步。那怎么保证WKWebView的cookie也同步到App的NSHTTPCookieStorage?在iOS11之后,苹果提供一个接口用于配置observer监听cookie的改变:

    WK_EXTERNAPI_AVAILABLE(macosx(10.13), ios(11.0))

    @interfaceWKHTTPCookieStore:NSObject

    /*! @abstract Adds a WKHTTPCookieStoreObserver object with the cookie store.

    @param observer The observer object to add.

    @discussion The observer is not retained by the receiver. It is your responsibility

    to unregister the observer before it becomes invalid.

    */

    - (void)addObserver:(id)observer;

    /*! @abstract Removes a WKHTTPCookieStoreObserver object from the cookie store.

    @param observer The observer to remove.

    */

    - (void)removeObserver:(id)observer;

    @end

    如果为了适配iOS11之前的版本,则需要native这边注册一个同步cookie的JS函数,用于h5将新的cookie同步到native这边。

    cookie不会自动覆盖?

    cookie会同名覆盖(保证上文说的四要素相同),但有时候后台可能下发的cookie某个要素不一致导致cookie不会自动覆盖,或者存在测试、生产域切换缓存,为了保险起见你可以每次重写cookie之前要先删除旧cookie:

    //清理cookie

    NSArray*oldCookies = [[NSHTTPCookieStoragesharedHTTPCookieStorage].cookiescopy];

    for(NSHTTPCookie*ckinoldCookies) {

    if([ck.name isEqualToString:@"capToken"]) {

    [[NSHTTPCookieStoragesharedHTTPCookieStorage] deleteCookie:ck];

            }

        }

    相关文章

      网友评论

          本文标题:WKWebView的cookie问题

          本文链接:https://www.haomeiwen.com/subject/ashpdhtx.html