问题很简单,开发一个后台服务。这个服务呢,包含了几个团队的后台服务之间的相互调用。这些后台服务之间的顶级域名(相关概念后面会解释)相同,请求却报错说有跨域问题。经调查,原因是调用方是http服务,被调用方是https服务,引起的跨域问题。如下图所示:
这里假设问题到生产环境才发现,本篇使用COE的写法来论述问题。COE(correction of error)更正错误,是很多公司用来做问题或者事故复盘的手段。COE因为名称中带着“有人犯错啦”的意思,很多公司为了表明“追究责任不是目的,重要的是吸取教训,避免同样和同类的问题”,把COE叫做casestudy个案研究。实践证明,不管叫什么,恐惧一点也不会减少。
事故描述
red服务从前端调用gray服务时报错:跨域错误。经排查确认由于http的域名下从前端调用https导致。
事故责任人
都是我的错
事故影响
新服务上线,无正式业务接入,无业务影响。
时间线
14:00 灰度发布新版本并进行验证
14:20 验证发现使用Chrome浏览器通过【检查】-【网络】功能观察到发起了一个OPTIONS类型的COR跨域请求,请求报错:
15:00 确认是http访问https导致违反了同源策略,导致跨域问题。新服务上线,无正式业务接入,无业务影响。所以未进行版本回滚。
根本原因分析
1、为什么会发生问题?
出于安全原因,浏览器限制从脚本内发起的跨源HTTP请求。当一个资源从与该资源本身所在的服务器不同源,请求一个资源时,资源会发起一个跨域 HTTP 请求。
同源的判断标准是:
- 协议
- 域名
- 端口号
以上三者都要相同。发生问题的请求,由于其中域名默认只要求顶级域名相同,此条件满足;协议一个是https,一个是http,不满足;https端口为443,http端口为80,不满足。总体不满足同源策略,而且被访问的后端没有做跨域处理。
2、为什么没有做跨域处理?
因为顶级域名相同,并未考虑到有跨域问题。且整个在测试环境下测试通过,并未发生问题。
3、为什么测试环境没有问题?
因为测试环境,同时支持http和https两种方式,当时配置时没有考虑两者直接的差异,直接使用http的路径。
在生产环境,根据安全的要求,关闭了http方式,只能使用https方式,造成问题。
4、为什么调用端使用http协议?
因为调用端的框架是几年前的老系统,线上环境追求稳定性,变更成本高,目前还维持之前的现状。
5、为什么调用方是post调用,实际上的请求却是options?
出于安全考虑,并不是所有域名访问后端服务都可以。其实在正式跨域之前,浏览器会根据需要发起一次预检(也就是option请求)。预检请求不成功,不会发起正式请求。
浏览器将CORS请求分为两类:简单请求(simple request)和非简单请求(not-simple-request),简单请求浏览器不会预检,而非简单请求会预检。
同时满足下列三大条件,就属于简单请求,否则属于非简单请求
- 请求方式只能是:GET、POST、HEAD
- HTTP请求头限制这几种字段:Accept、Accept-Language、Content-Language、Content-Type、Last-Event-ID
- Content-type只能取:application/x-www-form-urlencoded、multipart/form-data、text/plain
对于简单请求,浏览器直接请求,会在请求头信息中,增加一个origin字段,来说明本次请求来自哪个源(协议+域名+端口)。服务器根据这个值,来决定是否同意该请求,服务器返回的响应会多几个头信息字段,如图所示:上面的头信息中,三个与CORS请求相关,都是以Access-Control-开头。
- Access-Control-Allow-Origin:该字段是必须的,* 表示接受任意域名的请求,还可以指定域名
- Access-Control-Allow-Credentials:该字段可选,是个布尔值,表示是否可以携带cookie,(注意:如果Access-Control-Allow-Origin字段设置*,此字段设为true无效)
- Access-Control-Allow-Headers:该字段可选,里面可以获取Cache-Control、Content-Type、Expires等,如果想要拿到其他字段,就可以在这个字段中指定。
经验教训
首先是知识方面:之前对同域有误解,认为只需要顶级域名相同。实际上需要符合域名、协议和端口三者同源的同源策略。
其次是没有对线上线下的差异做仔细的调研分析。
后续优化
首先要修复问题,跨域有风险。可以使用调用方自带的代理功能。被调用方提供一个RPC调用,调用方使用代理来转换。
小知识
根域名
简单的来说就是类似.com这种,或者中国的.cn英国的.uk日本的.jp,这些是由“互联网名称与数字地址分配机构”(The Internet Corporation for Assigned Names and Numbers ,简称ICANN)来分配的。
顶级域名
简单来说顶级域名就是就是在根域名的前面加上你自己定义的字母或数字等字符串包括连字符-(包括连字符但是不能连续是连字符,且连字符不能在第一个)例如这样web.com或者web.cn这样的既是顶级域名。
二级域名
当你注册了一个顶级域名后,例如web.cn,你就可以在你的互联网服务提供商的dns解析系统上自由分配你的二级域名或者三级域名等等,web1.web.cn,web2.web1.web.cn等等,以此类推根域名前面有几个字符串就是几级域名。