首先,先解答几个常见的问题,部分问题了解的同学可跳过。
客户端拿到响应头中Set-Cookie字段后,PC浏览器会根据Cookie存储规则,自动存储到浏览器的数据库中,以便之后的请求,统一带上Cookie字段,Set-Cookie一般能设置有效期,也就是在有效期内,浏览器中直接访问业务地址,只要带上Cookie字段(sid=xxxxxx),业务地址就能直接访问成功。
要理解这个问题,首先得区分什么是Native(原生),什么是Web(网页),我们通过识别能力的提供者来理解Native和Web的区别,例如:在PC浏览器中,肉眼可见的布局、并且能够让用户点击交互的内容等,这是Web,调用系统通知,唤醒其他进程,历史记录查看,浏览器偏好设置,WebRTC等能力,这是Native,反复的思考这些能力的提供者是谁,就能理解Native,Web的区别。
App实际上是阉割版的浏览器,他有Web的渲染,JS的执行能力,但又缺失大部分Web所需要的Native能力,因此,很多PC浏览器能够使用的Native功能,在移动端,如果不和Native对接,几乎无法支持,回到Cookie的主题上,iOSApp则主要代表浏览器的Native能力,而App全局的Cookie管理,都归功于下面这个单例类:
//NSHTTPCookieStorage主要负责Native所有的Cookie管理[NSHTTPCookieStoragesharedHTTPCookieStorage].cookiesiOSWebView控件在iOS8之前,App使用的WebView控件主要以UIWebView为主,该控件和Native共享同一个Cookie管理,也就是上文中提到的单例类,因此Native中大部分网络请求返回的Set-Cookie,都能够被UIWebView继承(注意:同域下),很可惜UIWebView就只有Cookie上这个优点,随着Web技术的持续发展,UIWebView承担着多方的压力,也同时带来了各种问题:内存泄漏(暴涨)、渲染慢、安全性极低等,终于在iOS8开始,Apple推出了新版的WebView控件WKWebView,而UIWebView的生命也停止在了2020年。
很显然WKWebView在内存、渲染、安全等能力表现上都远超WKWebView,唯独一件事不如UIWebView,那就是对Cookie的管理。我们知道UIWebView的Cookie管理和Native共享,WKWebView的缓存、Cookie等信息,则使用独立的管理类:
//WKWebsiteDataStore主要负责WKWebView层面的资源缓存、Cookie等信息[WKWebsiteDataStoredefaultDataStore]即使知道这个API,也是在iOS9之后,因为这个API是iOS9提供的,在iOS8上根本无法访问Cookie,而且WKWebsiteDataStore并没有提供任何可以写入Cookie的方法,该类还不仅负责Cookie的数据,还有网页的其他元素(包括:协商缓存),可见Apple有多不想我们去碰Cookie,就这样,WKWebView被骂声、吐槽声包围,直到iOS11Apple良心发现,提供新的类,
WKWebView还有个特点,如果你在加载生命周期的最开始,自己改造Request在请求头中,塞入Cookie信息后,那么,后续Response中Set-Cookie的值不再被自动设置。
回到这个阶段的问题,为什么APP登陆了Web网页还要登陆一次?,因为,在iOS中,Native层和Web层的Cookie不再共享,Web层的Cookie独立管理,因此,Web得再重新登陆一遍。
最近,我们又收到iframePC页面打不开、白屏现象,原因是Set-Cookie没有自动带上Cookie信息导致的,所有该排查的点,我们都排查遍了,依旧没有找到解决方向,所以,也是做了高强度的测试验证。
首先,我们来复现问题,搭建复现环境,这里我是用nodejs、以及Express模拟服务端,增加如下接口:
welcomeb.html
一切准备就绪,我们利用Charles进行数据抓包,得倒了如下信息:
这里无限的重定向,和反馈的问题一致,无限重定向的原因,就是因为,在跨域条件下,Set-Cookie无法设置上去导致。
事实上,我经常会去点击系统Safrai的设置界面,我在iOS14(现在升级到iOS15)的时候,就注意到了设置界面多了几个选项:
一定和这些选项有关,Safrai的偏好设置,事实上在代码层面也有,主要和下面这个类对应,但Apple并不会把所有的API告诉你:
//WebView设置WKWebViewConfiguration//WebView偏好设置WKPreferences经过调研后发现,Apple在iOS14之后,开始启用一项技术——————“智能预防追中(IntelligentTrackingPrevention)”,简称ITP,
简单理解,启用这项技术之后,iframe在跨域的情况下,Cookie设置全部失效,实际上,我看这篇文档的时候,也就是今年是iOS15即将发布的时候,根据Apple最新的隐私政策,ITP还增加了“Cookie阻塞锁存模式”。
好吧,看样子,Apple是把iframe下Set-Cookie的路给堵死了,经过实际测试:
由于知道什么原因导致的,所以一切围绕“HowtodisableITPinWKWebView"这个主题进行,一番搜索之后,倒是找到了一个配置方案:
可以设置NSCrossWebsiteTrackingUsageDescription配置信息,允许跨站追踪,还是很兴奋地,结果非常失望,并没有起到任何作用,确实在软件的配置界面多了个按钮,但没有任何作用,甚至还误导了我的思考方向:
这个按钮,在iOS14上默认关闭,需要手动开下,在iOS15上默认开启,他娘的死活关不了,我用的iOS15-beta版本,是不是Bug就不得而知了,但有一点可以确认,这个按钮毫无用处。
在反复测试、验证这个配置项的过程中,我还得出了个结论,iOS14通过设置
//设置cookie的策略,永远接受[[NSHTTPCookieStoragesharedHTTPCookieStorage]setCookieAcceptPolicy:(NSHTTPCookieAcceptPolicyAlways)]上一次设置的Cookie,会在下次App冷启动的时候,自动带上,也就是说两者并非没有关联,这可能是iOS14的Bug,因为iOS15上这一特性完全没有了,这也引申出了最终的方案。
通过上一步的思考,以及得出的结论,我决定把WKWebView所有的请求全部代理出来,用Native发送请求,这样就可以和Native共享缓存。但是怎么做呢?在我的潜意识里,WKWebView的所有请求资源是不可代理,于是我瞄准了一个iOS11新提供的API:
成功了,并且Cookie也自动跳转了,也没有无限302,OK!,直接项目上运行,期望总伴随着失望,首次在项目运行,还是白屏,很郁闷,理论上都是对的,经过抓包分析如下:
原来在源码中,发生302跳转时,苹果是模拟一段页面跳转返回WKWebView,似乎资源路径找到了Location主机,有了前缀路径就对了,照搬这块逻辑:
资源全部正常访问,iframe打开成功:
当然后面,还有资源并发的问题,和被NSURLProtocol拦截后,如果不处理302状态码,得不到回调的问题,就不再详细描述,到这里基本临时解决方案有了。
临时方案不能作为最终方案,因为,这也只是阉割版的WebView的接口代理,仅仅处理了302状态码,实际上我看到Apple源码还有更多状态码,像307、308、303、206等状态码,我都没有对接,这相当于实现了一套WebView的网络交互标准
为了一个iframe问题,对接一套交互标准,我觉的意义不大,所以该方案有网络请求失败的风险,所以,还是建议针对iframe内嵌的业务场景,基本在移动端都可以弃用。