跨域的请求在服务端会不会真正执行 & CORS一些总结
跨域 cors 网络 前端
先说结论:
请求有的时候会被执行,有的时候不会执行。
那啥时候会执行,啥时候不会执行呢?其实这个问题主要要从以下几个方面去考虑:
- 跨域究竟是谁的策略?
- 在什么时机会拦截请求?
- 究竟什么时候会发预检请求?
- 如果有预检,请求什么时候会被真正执行?
跨域请求的拦截
有人认为请求不会执行,在服务端就会被拦截。
先想一下,服务端有什么责任和义务对跨域的请求做拦截呢?
首先我们俗称的跨域,也就是浏览器的 同源策略,直接看 MDN 的解释吧:

很明显是个浏览器的策略,与服务端无关。
如果服务端拦截,那每个每个 Server 都要专门要为浏览器实现一个拦截策略,这根本不现实。
另外,服务端就算是想拦截,也没法判断请求是否跨域,HTTP Reqeust 的所有 Header 都是可以被篡改的,它用什么去判断请求是否跨域呢?很明显服务端心有余而力不足。
在什么时候拦截
既然服务端不会拦截,那会不会是请求在浏览器发出去之前就被浏览器拦截了,请求根本发不出去呢?
这个问题先放放,大家可能都看过《解决跨域问题的 XXX 种方式》这样的文章,一般文章里都会告诉你用 CORS 去解决跨域。
大概的原理就是客户端会通过服务端返回的一些 Header 去判断该请求是否允许跨域:
比如,Access-Control-Allow-Origin 告诉客户端允许请求在哪些 Origin 下被发送,这些 Header 一般都是我们配在 Server 上的。
回到上面的问题,如果请求没发出去,这个 Header 是怎么被带回来的呢?浏览器又怎么知道 Server 允许请求在哪些 Origin 下跨域发送呢?
所以,我们又明确了一件事:请求一定是先发出去,在返回来的时候被浏览器拦截了,如果请求是有返回值的,会被浏览器隐藏掉。
预检请求
那请求既然被发出去了,服务端又不会拦截,所以一定会被执行吗?
当然不是,我们再回来把 CORS 这张图放大来看:

我们发现,在发送真正的请求之前,浏览器会先发送一个 Preflight 请求,也就是我们常说的预检请求,它的方法为 OPTIONS。
这也就是为什么有的时候我们明明只发了一个请求,在 Network 里却看到两个:

预检请求有一个很重要的作用就是 询问 服务端是不是允许这次请求,如果当前请求是个跨域的请求,你可以理解为:询问 服务端是不是允许请求在当前域下跨域发送。
当然,它还有其他的作用,比如 询问 服务端支持哪些 HTTP 方法。
预检的过程
当预检请求到达服务端时,服务端是不会真正执行这个请求的逻辑的,只会在这个请求上返回一些 HTTP Header,以此来告诉客户端是不是要发送真正的请求。
如果服务端告诉客户端,请求是允许被发送的,那真正的请求才会发出去。
比如:我在 a.com 这个 origin 下,发送了 b.com 这个域名的请求。
那么浏览器会先向 b.com 发送一个预检,预检请求不会真正执行这个域名的请求,而是返回了一些 CORS Header,比如 Access-Control-Allow-Origin: a.com
这时候浏览器发现, b.com 的请求是允许在 a.com 下发送的,才会真正发出请求。这时服务端才会真正执行请求接口的逻辑。
那么,所有的请求都会有预检吗?当然不是。
简单请求和复杂请求
预检请求虽然不会真正在服务端执行逻辑,但也是一个请求,考虑到服务端的开销,不是所有请求都会发送预检的。
一旦浏览器把请求判定为 简单请求,浏览器就不会发送预检了。
浏览器判定请求是否为简单请求要同时满足以下四个条件:
1.使用下列方法之一:
- GET
- HEAD
- POST
2.只使用了如下的安全 Header,不得人为设置其他 Header
- Content-Type 的值仅限于下列三者之一:
- text/plain
- multipart/form-data
- application/x-www-form-urlencoded
- Accept
- Accept-Language
- Content-Language
3. 请求中的任意 XMLHttpRequest 对象均没有注册任何事件监听器;XMLHttpRequest 对象可以使用 XMLHttpRequest.upload 属性访问。
4.请求中没有使用 ReadableStream 对象。
所以,如果你发送的是一个简单请求,这个请求不管是不是会受到跨域的限制,只要发出去了,一定会在服务端被执行,浏览器只是隐藏了返回值而已。
总结
- 简单请求:不管是否跨域,只要发出去了,一定会到达服务端并被执行,浏览器只会隐藏返回值。
- 复杂请求:先发预检,预检不会真正执行业务逻辑,预检通过后才会发送真正请求并在服务端被执行。
原文链接:https://mp.weixin.qq.com/s/-ahsfKWbRZq8U7epjJGk_w
CORS 一些总结:
由于同源策略的存在,导致我们在跨域请求数据的时候非常的麻烦。首先阻挡我们的所谓同源到底是什么呢?,所谓同源就是浏览器的一个安全机制,不同源的客户端脚本没有在明确授权的情况下,不能读写对方资源。由于存在同源策略的限制,而又有需要跨域的业务,所以就有了 CORS 的出现。
jsonp 也可以跨域,那为什么还要使用 CORS 呢?
- jsonp 只可以使用 GET 方式提交
- 不好调试,在调用失败的时候不会返回任何状态码
- 安全性,万一假如提供 jsonp 的服务存在页面注入漏洞,即它返回的 javascript 的内容被人控制的。那么结果是什么?所有调用这个 jsonp 的网站都会存在漏洞。于是无法把危险控制在一个域名下…所以在使用 jsonp 的时候必须要保证使用的 jsonp 服务必须是安全可信的。
CORS是一个 W3C 标准,全称是”跨域资源共享”(Cross-origin resource sharing),他允许浏览器向跨源服务器发送 XMLHttpRequest 请求,从而克服 AJAX 只能同源使用的限制
CORS需要浏览器和服务器同时支持,整个 CORS通信过程,都是浏览器自动完成不需要用户参与,对于开发者来说,CORS的代码和正常的 ajax 没有什么差别,浏览器一旦发现跨域请求,就会添加一些附加的头信息。
首先是上面已经说过的简单请求和非简单请求
对于简单请求来说,浏览器直接发送CORS请求,具体来说就是在头信息中,增加一个 origin 字段,来看一下例子:
GET /cors? HTTP/1.1
Host: localhost:8080
Connection: keep-alive
Origin: http://localhost:2332
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36
Accept: */*
Referer: http://localhost:8080/CORS.html
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
If-None-Match: W/"1-NWoZK3kTsExUV00Ywo1G5jlUKKs"
上面的头信息中,Origin字段用来说名本次请求来自哪个源,服务器根据这个值,决定是否同意这次请求。
如果Origin指定的源不在允许范围之内,服务器就会返回一个正常的 HTTP 回应,然后浏览器发现头信息中没有包含Access-Control-Allow-Origin 字段,就知道出错啦,然后抛出错误,反之则会出现这个字段:
Access-Control-Allow-Origin: http://a.com
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers: FooBar
Content-Type: text/html; charset=utf-8
- Access-Control-Allow-Origin 这个字段是必须的,表示接受那些域名的请求(*为所有)
- Access-Control-Allow-Credentials 该字段可选, 表示是否可以发送 cookie
- Access-Control-Expose-Headers 该字段可选,XHMHttpRequest 对象的方法只能够拿到六种字段: Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma ,如果想拿到其他的需要使用该字段指定。
如果需要连带 Cookie 一起发送,是需要服务端和客户端配合的
// 服务端
Access-Control-Allow-Credentials: true
// 客户端
var xhr = new XMLHttpRequest();
xhr.withCredentials = true;
// 但是如果省略withCredentials属性的设置,有的浏览器还是会发送cookie的
xhr.withCredentials = false;
对于非简单请求来说, CORS请求是会在正式通信之前进行一次预检请求,这个在上面也有提过了。
当浏览器发现这是一个非简单请求的时候,会自动发出预检请求,看看服务器可不可以接收这种请求,下面是”预检”的 HTTP 头信息:
OPTIONS /cors HTTP/1.1
Origin: localhost:2333
Access-Control-Request-Method: PUT // 表示使用的什么HTTP请求方法
Access-Control-Request-Headers: X-Custom-Header // 表示浏览器发送的自定义字段
Host: localhost:8080
Accept-Language: zh-CN,zh;q=0.9
Connection: keep-alive
User-Agent: Mozilla/5.0...
“预检”使用的请求方法是 OPTIONS , 表示这个请求使用来询问的,
预检请求后的回应,服务器收到”预检”请求以后,检查了Origin、Access-Control-Request-Method和Access-Control-Request-Headers字段以后,确认允许跨源请求,就可以做出回应。
预检的响应头:
HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:15:39 GMT
Server: Apache/2.0.61 (Unix)
Access-Control-Allow-Origin: http://localhost:8080 // 表示http://localhost:8080 可以访问数据
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: X-Custom-Header
Content-Type: text/html; charset=utf-8
Content-Encoding: gzip
Content-Length: 0
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Content-Type: text/plain
如果浏览器否定了”预检”请求,会返回一个正常的 HTTP 回应,但是没有任何CORS的头相关信息,这是浏览器就认定,服务器不允许此次访问,从而抛出错误。
预检之后的请求:
当预检请求通过之后发出正经的 HTTP 请求,还有一个就是一旦通过了预检请求就会,请求的时候就会跟简单请求,会有一个Origin头信息字段。
通过预检之后的,浏览器发出发请求:
PUT /cors HTTP/1.1
Origin: http://a.com // 通过预检之后的请求,会自动带上Origin字段
Host: api.alice.com
X-Custom-Header: value
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...
原文链接:https://juejin.cn/post/6983852288091619342