一个favicon.ico图标引发的重大事故

也许很难让人相信,一个favicon.ico图标会引起重大事故,而我真的就亲身经历了一回。最近上线的一个项目出现了匪夷所思的事情。如果不去抓包分析,就真的就可以称得上玄学了。

这个项目分为API后端和Android客户端以及嵌入WebView的网页。客户端除了登录和一些基本的API是原生操作以外,其他核心业务都是在WebView加载的网页上进行交互的。项目发布生产环境上线后,部分机型的用户反应核心功能无法使用,在一定程度上造成了很严重的问题。最终定位到问题在ico图标,下面进行详细的分析。
在App客户端中嵌入WebView,进行原生和非原生的混合开发在现今依然是成本迭代的产物。在很多场合下API之间的请求越来越多的采用Token机制代替Session,但是Session也有自己的一套自己的完善方案来保证API安全,这里不做Token和Session的对比。

由于项目的特殊性,客户端和服务端的API接口采用Session机制

测试的主要问题如下:

1.APP客户端对测试环境的所有请求完全正常,但是在生产环境下,后续页面中的Ajax请求的核心业务异常
2.PC浏览器打开对应的网页,无论测试环境还是生产环境,全部都是正常的

针对这个问题,是无法从编码的角度发现的,所以必须抓包分析一下区别
根据抓包的数据分析并且找到了问题所在,我把抓到的包 通过下面的测试环境和生产环境的请求时序图简要的罗列了出来。

一、测试环境的请求

在混合开发中,客户端和服务端正常的请求响应如下图所示:

Android客户端有原生的API请求和WebView请求两个部分,其中登录请求是用Android原生的请求实现的,
并且把登录后得到的SessionID通过Cookie同步设置给WebView。这样WebView在后续请求时会自动带上
其中绿色的生命线部分代表客户端的WebView(为了偷懒这里简化了时序图)

二、生产环境的请求

通过时序图可知,生产环境下部署了很多项目,其中有个WEB服务的域名为www.pro-web.com,而我们的后端API服务部署在这个域名的子目录下面:www.pro-web.com/api/

低版本Android的WebView在发起请求时,默认会请求对应域名的favicon.ico资源。这个域名改好被WEB服务A拦截,重新分配了一个SESSIONID给WebView,通过响应头的Set-Cookie将旧的Cookie中的SESSIONID覆盖。由于这个SESSIONID是WEB服务A分配的,导致后续的Ajax请求API服务端时,API服务无法根据SESSIONID找出对应的SESSION,认为该用户没有登录,所以无法进行正常的业务请求。

二、得出的结论

由于Android不同版本号的WebView的请求方式有区别,所以在请求接口地址www.pro-web.com/api/的资源时,会自动请求www.pro-web.com域名下的favicon.ico图标。

而且刚好www.pro-web.com这个域名下有其他的Web服务拦截了ico请求,并且重定向到了这个Web服务的其他业务,导致重新分配了一个改Web服务对应的SESSIONID,通过返回体头中的Set-Cookie自动将客户端原本的SESSIONID覆盖。造成了后续服务的不可用。

解决方案

1.android的WebView设置不请求网站的favicon.ico资源
2.在xxx.html中添加ico资源,优先级最高,这样就不会再去请求ico资源
3.相同域名下如果有多个项目,需要为每个项目配置各自的上下文,不能出现上下文嵌套的情况
4.使用Token机制进行API接口验证,这样就避免了服务端拦截资源后Set-Cookie把客户端登录时正常的SESSIONID覆盖

方案1:考虑到android开发比较忙,沟通的成本比较大
方案3:旧项目的生产环境地址已经用了很久,修改上下文可能会造成其他的服务调用问题。
方案4:从Session到Token迁移需要很大的工作量,尤其是已经上线的服务比较多的时候,会牵一发而动全身。

所以最终采用方案2,在API服务端这里做修改
xxx.html的head中添加如下:

1
<link rel="icon" href="data:image/ico;base64,aWNv">

建议:将每个项目都用不同的二级域名
注:即使设置了二级域名,某些内核版本的浏览器依旧会请求favicon.ico图片,如果二级域名找不到,就会到顶级域名查找。如果顶级域名下对应的项目有资源拦截的业务把ico拦截了,那么依然有可能出问题,所以服务端一定要规范。有的时候妥协不是为了相互甩锅和接锅,是为了整个项目能够正常进行,要不断进行尝试,为大局着想。

0%