跨域与预请求
跨域
当前发起请求的域与该请求指向的资源所在的域不一样,就是跨域。
这里所说的域是指协议+域名+端口号,如果这三者都完全一致,则是同域请求,只要有一个不同,就是跨域请求。
为什么会跨域呢,这是由于浏览器的同源策略导致?
同源策略
什么是同源策略及其限制内容?
同源策略(Same Origin Policy)是浏览器的一种安全机制,它用于限制一个网页或脚本与另一个网页或脚本之间的交互操作。同源策略的核心原则是,只有当两个网页或脚本具有相同的协议
、域名
和端口号
时,它们才被认为是同源的,才能进行交互。这种限制的目的是保护用户的隐私和安全,防止跨站点攻击,如跨站脚本攻击(XSS)和跨站请求伪造攻击(CSRF)。
同源策略限制了多种资源和操作,包括但不限于:
Cookie、LocalStorage、IndexedDB 等存储在浏览器中的数据只能被同源网页访问。
XMLHttpRequest和Fetch等网络请求API只能向同源网址发送请求。
DOM 节点操作也受到同源策略的限制,一个网页只能修改同源网页的DOM。
为了实现跨域资源共享,浏览器提供了一些例外机制,如跨文档消息传递(postMessage)和跨源资源共享(CORS)。这些机制允许在满足一定条件的情况下,来自不同源的资源可以进行有限的交互。
跨源资源共享(CORS)
跨源资源共享(CORS,或通俗地译为跨域资源共享)是一种基于 HTTP 头的机制,该机制通过允许服务器标示除了它自己以外的其他源(域、协议或端口),使得浏览器允许这些源访问加载自己的资源。跨源资源共享还通过一种机制来检查服务器是否会允许要发送的真实请求,该机制通过浏览器发起一个到服务器托管的跨源资源的“预检”请求。在预检中,浏览器发送的头中标示有 HTTP 方法和真实请求中会用到的头。
跨源 HTTP 请求的一个例子:运行在 https://domain-a.com
的 JavaScript 代码使用 XMLHttpRequest
来发起一个到 https://domain-b.com/data.json
的请求。
出于安全性,浏览器限制脚本内发起的跨源 HTTP 请求。例如,XMLHttpRequest
和 Fetch API 遵循同源策略。这意味着使用这些 API 的 Web 应用程序只能从加载应用程序的同一个域请求 HTTP 资源,除非响应报文包含了正确 CORS 响应头。
CORS 机制允许 Web 应用服务器进行跨源访问控制,从而使跨源数据传输得以安全进行。现代浏览器支持在 API 容器中(例如 XMLHttpRequest
或 Fetch)使用 CORS,以降低跨源 HTTP 请求所带来的风险。
什么情况下需要 CORS
这份跨源共享标准允许在下列场景中使用跨站点 HTTP 请求:
- 前文提到的由 XMLHttpRequest或 Fetch API发起的跨源 HTTP 请求。
- Web 字体(CSS 中通过
@font-face
使用跨源字体资源),因此,网站就可以发布 TrueType 字体资源,并只允许已授权网站进行跨站调用。 - WebGL 贴图。
- 使用 drawImage()将图片或视频画面绘制到 canvas。
- 来自图像的 CSS 图形 (en-US)。
有的跨域请求为什么会发送两次?
跨域请求属于非简单请求时,在正式发起跨域http请求之前,浏览器会根据需要发起一次预检(OPTIONS请求),用来让服务端返回请求允许的方法(如get、post),orgin(来源|域名),以及是否需要Credentials(认证信息)等。
预请求OPTIONS
预检请求仅在以下情况下发送:
- 使用跨域资源共享(CORS)进行跨域请求。
- 请求方法为非简单请求,如PUT、DELETE等。
- 请求使用了特殊的自定义请求头部字段。
如果请求满足以上三个条件,则浏览器会在实际请求之前发送预检请求。预检请求使用OPTIONS方法发送,并包含一些额外的头部信息,以确保服务器接受实际请求。
预检请求的目的是为了确保非简单请求对服务器的影响是安全和可控的。服务器必须正确处理预检请求,并在响应中包含适当的CORS头部,以指示允许实际请求。
对于简单请求(满足简单请求条件的请求),浏览器不会发送预检请求,而是直接发送实际请求,并将响应返回给请求方。
因此,只有在跨域请求满足非简单请求的条件时,浏览器才会发送预检请求。对于简单请求,即使涉及跨域,浏览器会直接发送实际请求。
简单请求
并非所有的跨域请求都会触发预请求。满足如下所有条件,即使跨域,也不会触发预检请求,一般称为简单请求。
- 使用 GET、HEAD、POST 方法
- Content-Type 只能是 text/plain、 multipart/form-data、 application/x-www-form-urlencoded
- 不添加自定义头部信息,请求头限制这几种字段:Accept、Accept-Language、Content-Language、Content-Type、Last-Event-ID
对于简单请求,浏览器直接请求,会在请求头信息中,增加一个origin字段,来说明本次请求来自哪个源(协议+域名+端口)。服务器根据这个值,来决定是否同意该请求。服务器返回的响应会多几个和跨域相关的头信息字段
- Access-Control-Allow-Origin:该字段是必须的,* 表示接受任意域名的请求,还可以指定域名
- Access-Control-Allow-Credentials:该字段可选,是个布尔值,表示是否可以携带cookie,(注意:如果Access-Control-Allow-Origin字段设置*,此字段设为true无效)
- Access-Control-Allow-Headers:表明服务器允许请求中携带字段 ,如Cache-Control、Content-Type、Expires等
- Access-Control-Max-Age:有效时间,在有效时间内,浏览器无须为同一请求再次发起预检请求
非简单请求
非简单请求(Non-Simple Request)是指在使用跨域资源共享(CORS)进行跨域请求时,不符合简单请求条件的请求类型。根据CORS规范,浏览器对简单请求和非简单请求采取了不同的处理方式。
对于非简单请求,浏览器会在实际请求之前发送一个预检请求(Preflight Request)来检查服务器是否允许该请求。预检请求使用OPTIONS方法发送,并包含一些额外的头部信息,以确保服务器接受实际请求。
预检请求的目的是为了确保非简单请求对服务器的影响是安全和可控的。服务器必须正确处理预检请求,并在响应中包含适当的CORS头部,以指示允许实际请求。
预检请求包括以下信息:
- 请求方法为OPTIONS。
- Access-Control-Request-Method 头部字段指示实际请求的方法。
- Access-Control-Request-Headers 头部字段指示实际请求中包含的额外头部信息。
预检请求时浏览器会先询问服务器,当前网页所在域名是否在服务器的许可名单之中,服务器在收到预检请求后,应根据请求的方法、头部字段和其他相关信息来判断是否允许实际请求的访问,并在预检响应中返回相应的CORS头部。
服务器允许之后,浏览器会发出正式的XMLHttpRequest请求,否则会报错。
非简单请求的处理相对复杂,因为涉及到预检请求和服务器的支持。因此,在进行跨域请求时,需要了解请求是否属于简单请求,并在服务器端进行相应的配置和处理。
看看下面图片:
很明显,请求头中预检请求不会携带cookie,正式请求会携带cookie和参数。一旦服务器通过了“预检”请求,以后每次浏览器正常的CORS请求,就都跟简单请求一样。若后端设置了拦截器拦截请求,并对不携带cookie或者token的请求不放行。此时应该考虑到放行options请求,因为该请求header中不会携带cookie或者token。