摘要
谈到跨域,不论前端还是后端,多少有点谈虎色变,面试中也常会问到这些问题,浏览器和服务器端到底怎么做才能跨域,他们都做了什么?
同源 vs 跨域
同源,字面意义是相同的源头,即同一个 web 服务器(比如 tomcat 启动的一个实例),好比一个家庭;跨域就是从一个 web 服务器向另一个服务器发送或获取数据,好比去邻居家拿东西。之所以要做跨域限制,是为了安全,你不能从邻居家直接拿吧,总得问过人家。
那么怎么来判断同源?就看一个 web 服务器的根本三要素:协议、web 服务器域名、端口号。
比如:当前页面的地址是 http://myhost.com:8080/index, 源就是 http://myhost.com:8080, 当新请求的协议、域名、端口号与之完全一致即是同源,否则是跨域。
对于下面发送的请求,浏览器的判决如下:
http://www.myhost.com:8080/users 跨域 -> 域名必须完全一致
https://myhost.com:8080/users 跨域 -> 协议必须一致
https://myhost.com:9000/users 跨域 -> 端口必须一致
https://myhost.com/users 跨域 -> 端口必须一致 (没有端口也是不一致)
https://myhost.com:8080/profile/users 同源
跨域时的动作
浏览器端
假设我们点击一个按钮去获取数据,获取数据的请求准备从浏览器发出,这时浏览器先会检测这条请求是同源还是跨域,也就是与按钮所在页面的地址是同源还是跨域,如果是同源,好说,直接发送出去;如果是跨域的请求,那就得 hold 住先,浏览器会在请求的 http header 中加上一个 Origin 字段,标明这个请求是从哪里发出来的,例如: Origin:http://neighbour.com:9000, 这样服务器端好辨识是自己家人来取东西,还是隔壁老王来借东西了。
那些做检测、加 header 字段等事情全是浏览器做,对于前端开发者来说,什么事都不用干,ajax 请求平时怎么发送,跨域时还怎么发送。可见,跨域对于前端没影响,关键在于服务器端。
服务器端
每个 web 服务器就像一个家庭。好邻居要吃螃蟹来借点醋,这没问题;坏邻居家有醋要来借点螃蟹,这不干。
服务器收到请求会给与响应,响应的 header 里写明跨域的配置信息,告诉浏览器,它允许哪些域名发来的请求访问,哪些 method 可以执行。浏览器收到响应后自动判断能不能真正执行请求。
假设服务器域名是 http://rich.com:8080,它收到了一个从 http://neighbour.com:9000 发来的请求 http://rich.com:9000/borrow-vinegar,服务器响应它,在响应中写明本服务器支持哪些域名可以访问,哪些 method 可以执行,浏览器收到后做匹配,再判定。
那么,服务器有哪些判定的规则呢?
是否允许跨域的判定
一个支持 CORS 的 web 服务器,有如下的判定字段,他们会在响应的 header 中写明
Access-Control-Allow-Origin:允许跨域的 Origin 列表
Access-Control-Allow-Methods:允许跨域的方法列表
Access-Control-Allow-Headers:允许跨域的 Header 列表
Access-Control-Expose-Headers:允许暴露给 JavaScript 代码的 Header 列表
Access-Control-Max-Age:最大的浏览器缓存时间,单位为 s
其中 Access-Control-Allow-Origin(访问控制之允许的源), 在响应的 http header 中必须有的,表示允许访问本服务器的源头 Origin(域名),可以是特定的域名列表,用逗号分隔,也可以是通配符 *,表示支持任意域名的访问。
除了限定源头 Origin,还会限制请求的方法 Method,Header。
如,如果服务器设定 Access-Control-Allow-Methods:GET,那么跨域的 POST 请求无法在这个服务器执行。
总流程
页面发送请求
浏览器根据同源策略做出判定,如果是同源请求,直接发送出去;如果是跨域请求,在 HTTP HEADER 加上 Origin 字段,或是先发送一次预检请求 (preflight)。
服务器接收请求,根据自身跨域的配置(如允许哪些域名,什么样的 Method 访问),返回文件头。若未配置过任何允许跨域,则文件头里不包含 Access-Control-Allow-origin 字段,若配置过域名,则返回 Access-Control-Allow-origin+ 对应配置规则里的域名的方式。
浏览器接收到响应,根据响应头里的 `Access-Control-Allow-origin 字段做匹配,如果没有这个字段,说明不匹配;如果有,将字段内容和当前域名做比对。如匹配,则可以发送请求。
跨域的请求形式
跨域的请求分两种,一种是简单请求,一种是非简单请求(废话)。
简单请求,方法仅限于 HEAD,GET 或 POST,且 Header 的字段不超过以下字段:
HeadeAccept
Accept-Language
Content-Language
Last-Event-ID
Content-Type:只限于三个值 application/x-www-form-urlencoded、multipart/form-data、text/plain(没有 application/json, 说明如果发送 JSON 格式的 body 请求数据是一个非简单请求 )
非简单请求就是其他请求
简单请求浏览器会直接在请求的 Header 加上 Origin 字段再发送;非简单请求浏览器则会先发送一次预检请求,根据预检请求的结果,决定是否正式发送请求。
详情可以访问阮一峰的日志:跨域资源共享 CORS 详解