40- WKWebView项目实践分享(五)- WKWebView如何加Cookie

系列文章:

在iOS11以下的版本中,WKWebView并没有类似UIWebView中NSHTTPCookieStorage这样的可以直接设置和存储Cookie的官方类。但是在各个App的实际开发过程中,给HTML5后台传入Cookie进行身份校验等属于最基本的操作,正是因为不能方便设置Cookie的原因,在本文发布的时候,很多大厂App的核心web业务并没有使用WKWebView,仍然是UIWebView。iOS11开始,苹果意识到了这个问题,做了妥协,新增WKHTTPCookieStorage来帮助开发者解决Cookie问题,虽然方便了,但是API仍然不稳定,比如iOS11.3的时候就出现了一些变动。

针对WKWebView设置Cookie的问题,为什么在iOS8-iOS11长达三年的时间里,苹果没有理会呢?CSDN作者请叫我马老师的一篇文章中,提出了猜想,苹果是想让服务端完全控制Cookie,毕竟客户端操作Cookie是有一定安全风险的。WKWebView设计出来,对安全性就有比较高的要求,比如说对跨域的限制。

客户端不传Cookie之后,服务端怎么确认用户的身份信息呢?其中的一种方式,我们可以在首次请求的时候,将用户信息身份信息通过POST请求的方式放到requst中。服务端接收到首次请求,判断用户身份信息。如果身份信息有效,在之后的请求中,服务器会主动设置Cookie保证后续的请求身份验证是通过的。如果身份信息无效,则跳转到登录页面。 这种方式就是Cookie完全交给服务器管理了。

但是,实际开发中,我们还是免不了客户端设置Cookie。下边说说在WKWebView中设置Cookie的方案。

WKWebView中设置Cookie的方案

在UIWebView中,可以通过NSHttpCookieStorage来管理Cookie,对Cookie进行增删改查。当把Cookie添加到NSHttpCookieStorage中后,webView在发起请求的时候会自动从NSHttpCookieStorage中取出Cookie添加到request Header中,并且这个操作是零延迟的。也就是说,你第一行代码添加Cookie到NSHttpCookieStorage中,第二行代码loadRequest:,发起的请求中是带有你设置的Cookie的。
关于零延迟这一点非常重要,因为你设置了Cookie,但是请求发起之后,request Header拿不到你设置的Cookie,导致服务器找不到Cookie,从而身份校验不通过。

NSHttpCookieStorage对WKWebView而言,只能管理Cookie,但是没有了零延迟自动同步到request Header这一关键特性。所以我们只能另找出路。正式因为修正这个问题,我们需要找对应方案适配不同的版本

截至本文发布时候,需要适配iOS版本分为三个范围,分别是iOS8iOS11.0、iOS11.0iOS11.2、iOS11.3,iOS11.3以后的版本因为有了官方APIWKHTTPCookieStorage的加持,可以直接套用iOS11.3的方案。 下边看方案的具体实施:

Cookie版本适配方案

下边就针对每个适配版本来说一下具体操作方案。

适配iOS8~iOS11.0

首先这一步适配需要关注的地方有两处:

  • 第一处是我们每次调用loadRequest:方法的时候,需要加Cookie。
  • 另一处就是loadRequest:成功之后,webView的后续页面跳转的时候,也需要加Cookie。

第一处,我们的解决思路是使用NSHttpCookieStorage管理Cookie。然后在每次调用loadRequest:方法的时候,我们把Cookie手动写入到request Header中。具体代码和步骤如下:

// 1. 清除NSHTTPCookieStorage中存储的Cookies,避免重复Cookie
NSHTTPCookieStorage *cookieJar = [NSHTTPCookieStorage sharedHTTPCookieStorage];
for (NSHTTPCookie *cookie in cookieJar.cookies) {
    if ([cookie.comment isEqualToString:WebCookieComment]) {
        [cookieJar deleteCookie:cookie];
    }
}

// 2. 重新设置Cookies
[[NSHTTPCookieStorage sharedHTTPCookieStorage] setCookie:cookie];
[[NSUserDefaults standardUserDefaults] synchronize];

// 3. 向Http Header中设置Cookie    
 NSDictionary *dict = [NSHTTPCookie requestHeaderFieldsWithCookies:[NSHTTPCookieStorage sharedHTTPCookieStorage].cookies];
 request.allHTTPHeaderFields = dict.copy;

// 4. 加载请求
[self.wkwebView loadRequest: request];

第二处后续webView跳转中加Cookie,我们的解决思路是把Cookie添加到HTML中的document.Cookie,代码如下:

//1. 添加自定义的cookie,后续请求Ajax页面用到
WKUserScript *newCookieScript = [[WKUserScript alloc] initWithSource:[WebViewCookieTool cookieJavaScriptString] injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:NO];
[contentController addUserScript:newCookieScript];
webConfiguration.userContentController = contentController;


//2. 获取Cookie的JS代码
+ (NSString *)cookieJavaScriptString
    NSMutableString *script = [NSMutableString string];
    for (NSHTTPCookie *cookie in [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies]) {
    if ([cookie.value rangeOfString:@"'"].location != NSNotFound) {
         continue;
    }
    [script appendFormat:@"document.cookie='%@'; \n", [self getCookieJSFromatString:cookie]];
}
    
    
+ (NSString *)getCookieJSFromatString:(NSHTTPCookie *)cookie
{
    NSString *cookieStr = [NSString stringWithFormat:@"%@=%@;domain=%@;path=%@",
                           cookie.name,
                           cookie.value,
                           cookie.domain,
                           cookie.path ?: @“/"];
    return cookieStr;
}

这种方案出现的问题

这种方案基本解决了iOS8-iOS10设置Cookie的问题,但是在实测中,发现将Cookie添加到request Header中会导致另一个问题出现。那就是如果在webView带着Cookie发起请求之后,HTML服务器如果也想设置一些Cookie到header中,是不能正常设置的或者说添加不上,现象就是网页无法正常显示。针对这个问题,想到了两个解决方案:

解决服务器无法在Header中正常设置Cookie

所以,我项目里有一个区分是不是本公司请求的静态方法:

static inline BOOL checkURLIsNeedCookies(NSURL *url) {
    if ([url.host hasSuffix:@".myCompany.com") {
        return YES;
    }
    return NO;
}

适配iOS11.0~iOS11.2

iOS11.0之后的适配就爽啦,苹果在iOS11.0推出了WKHTTPCookieStore,这个类对WKWebView的效果和NSHttpCookieStorage对UIWebView的效果一样,并且也是零延迟。在请求发起的时候,我们只需要将Cookie设置到WKHTTPCookieStore就可以了。代码如下:

// 1. 设置Cookie
WKWebsiteDataStore *store = [WKWebsiteDataStore defaultDataStore];
[store.httpCookieStore setCookie:cookie completionHandler:^{
NSLog(@“cookie添加成功”);
}];

// 2. 加载请求
[self.wkwebView loadRequest: request];

适配iOS11.3

前几天升级iOS11.3之后发现一个问题。具体出错的位置是需要上传Cookie的HTML页面,WKHTTPCookieStore第一次设置Cookie之后,发起请求,Cookie不能同步到request Header中。此时,webView重新请求页面,WKHTTPCookieStore的作用恢复正常,之后请求其他的webView也不会出现Cookie丢失的问题。 总结就是在iOS11.3中,WKHTTPCookieStore设置Cookie之后,第一次发起请求Cookie会丢失,第二次及以后的请求的Cookie没有问题。

解决方式是,我们在执行loadRequest之前主动设置一次Cookie。我这里是在didFinishLaunchOption:中设置的。设置完之后,就没有问题了。

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 
{
   WKWebsiteDataStore *store = [WKWebsiteDataStore defaultDataStore];
    [store.httpCookieStore setCookie:cookie completionHandler:^{
        NSLog(@“cookie添加成功”);
    }];
}

好了,关于WKWebView的Cookie设置就到此为止。

共享Web Content Process

我们之前说过,一个WKWebView创建出来之后,对应一个Web Content Process。这样就保证每个WKWebView的缓存、Cookie等信息是隔离的。但是这样会导致一个问题就是,当网页中出现<a>标签和target=_blank的时候,会好像浏览器那样新开一个tab页面,此时新开的webView不共享不到之前webView的Cookie的,导致新开的webView不能正常显示。 处理的方式是,所有的WKWebView共享同一个processPool。 下面有两种设置方式:

 1’:   
self.processPool = [[WKProcessPool alloc] init];
WKWebViewConfiguration *configuration1 = [[WKWebViewConfiguration alloc] init];
configuration1.processPool = self.processPool;
WKWebView *webView = [[WKWebView alloc] initWithFrame:CGRectZero configuration:configuration1];


2’:
- (WKProcessPool *)wkProcessPool
{
    static dispatch_once_t once;
    static WKProcessPool * __singleton__;
    dispatch_once( &once, ^{
        __singleton__ = [[WKProcessPool alloc] init];
        
    });
    return __singleton__;
}

 webConfiguration.processPool = self.wkProcessPool; 
 WKWebView *webView = [[WKWebView alloc] initWithFrame:CGRectZero configuration:configuration1];

因为设置了单利的缘故,我们在HTML页面退出的时候,因为销毁单利。否则会导致一些问题。在《42- WKWebView项目实践分享(六) - 补充: 实践中的坑》提到。

参考

交流


希望能和大家交流技术
Blog:http://www.lilongcnc.cc


最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容