【HTTP:什么是跨源资源共享(CORS)?详解:控制“跨源访问”】

跨源资源共享 CORS

  • [跨源资源共享 CORS (控制 跨源访问)](#跨源资源共享 CORS (控制 跨源访问))
    • [⑴ 浏览器 请求控制 (默认:允许同源请求、拒绝跨源请求)](#⑴ 浏览器 请求控制 (默认:允许同源请求、拒绝跨源请求))
    • [⑵ 浏览器的 两种跨源请求(简单请求、非简单请求)](#⑵ 浏览器的 两种跨源请求(简单请求、非简单请求))
    • [⑶ 跨源请求 CORS 相关的 HTTP 头信息 (表格)](#⑶ 跨源请求 CORS 相关的 HTTP 头信息 (表格))
    • [⑷ 带预检请求 OPTIONS 的 跨源请求流程(非简单请求、浏览器自动发起)](#⑷ 带预检请求 OPTIONS 的 跨源请求流程(非简单请求、浏览器自动发起))
    • [♣ 结束语 和 友情链接](#♣ 结束语 和 友情链接)


跨源资源共享 CORS (控制 跨源访问)

跨源资源共享 (又称 跨域资源共享CORS:Cross-Origin Resource Sharing):是一种机制,该机制基于 HTTP 头,通过 设置 "一组 HTTP 标头字段",控制 "跨源访问"

  • CORS 相关 HTTP 头 设置在 请求 和 响应的 HTTP 头信息中。跨源访问 相关的 HTTP 头信息字段 见 第⑶ 小节。

    • 请求中的 HTTP 头信息 ⇒ 提供网站 自身的信息,方便服务器判断,如 请求来源的网址、请求的方法、请求头 等。
    • 响应中的 HTTP 头信息 ⇒ 提供服务器的响应信息,服务器根据请求内容 做出判断,允许哪些来源访问、允许的方法、允许的头、响应结果的最大缓存时间 等。
  • 使用 跨源访问 CORS 理由 :如,有时候,我们跨源访问别人家的资源时,要经过别人的同意,CORS 可以让别人通过服务器设置 "是否同意访问请求,同意哪种请求",也可以让我们提供自身网站的一些信息,让对方服务器进行判断。

  • :跨源请求访问 CORS 通信的整个过程,由 浏览器自动完成 ,跟 用户没关系。浏览器会自动判断 是不是跨源请求,要发什么 HTTP 请求信息,而服务器端只要实现了 CORS 相关设置,就可以跨源访问。


跨源资源共享 CORS 机制的用处服务器可以"控制 跨源访问",使跨源的数据传输 更安全。

  • 谁能跨源访问 (服务器上设置)
    • 服务器上可以设置允许 或 拒绝 "跨源访问请求" ,允许的话,别人就能跨源访问这个服务器的资源。这个设置的目的 就是告诉浏览器"谁可以访问 我服务器的资源?"
    • 跨源:协议、域名、端口有一个不同,就算是跨源。
  • 哪些跨源请求 是被允许的(服务器上设置)
    • "预检"请求 :简单请求不需要预检请求,而非简单请求,服务器上可以设置 允许的请求 ,预检请求的目的就是确认 "服务器,你允许我的这种请求吗?"。
    • 让浏览器使用 OPTIONS HTTP 方法,发起一个到服务器的 跨源资源的 "预检"请求 ,检查服务器是否允许要发送的真实请求。在预检中,浏览器发送的头中 有 HTTP 请求方法 、真实请求中会用到的请求头
    • 基本流程 :❶ 针对非简单请求,浏览器自动发起一个预检请求OPTIONS 方法 )⇒ ❷ 服务器响应:确定服务器是否允许该跨源请求 ⇒ ❸ 确认服务器允许 ⇒ ❹ 浏览器 发起实际的 HTTP 请求
      • 服务器端 对预检请求的响应 中,也可以通知客户端,是否需要携带身份凭证(如 Cookie 和 HTTP 认证相关数据)。

  • 一个简单的 跨源请求

    • 网站 A (https://foo.example )的网页 想要访问 网站B ( https://bar.other) 的资源。
    • 能不能实现 客户端和服务器之间的跨源访问,使用 CORS 标头字段 来处理权限
    • 最简单的 跨源访问控制 :使用 浏览器-请求头信息的 Origin 字段 和 服务器-响应头信息的 Access-Control-Allow-Origin 字段就能完成 最简单的跨源访问控制。
  • 如 网站A 的网页中 可能包含类似于下面的 JavaScript 代码:

  • 第一步 ,网站 A 的网页 js 中 有跨源请求

javascript 复制代码
		const fetchPromise = fetch("https://bar.other");
		
		fetchPromise
		  .then((response) => response.json())
		  .then((data) => {
		    console.log(data);
		  });
  • 第二步 ,浏览器处理跨源请求,发给服务器的请求头信息 添加 Origin 头信息。
    • 简单的请求,浏览器会判定 不需要预检请求(OPTIONS 请求) ,直接发送 实际的 HTTP 请求,对于复杂的请求,浏览器也会自动发出预检请求,不需要手动设置。
    • Origin ⇒ 指明 该请求来源网址 = http://foo.example
css 复制代码
		GET /resources/public-data/ HTTP/1.1
		Host: bar.other
		User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:71.0) Gecko/20100101 Firefox/71.0
		Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
		Accept-Language: en-us,en;q=0.5
		Accept-Encoding: gzip,deflate
		Connection: keep-alive
		Origin: https://foo.example
  • 第三步 ,服务器收到请求后,根据 Origin 给出响应。
    • ❶ 该资源可以被任意 外部资源 访问 ⇒ Access-Control-Allow-Origin: *
    • ❷ 限制 访问者只能是某网址:允许源= 具体网址Access-Control-Allow-Origin: https://foo.example
    • ⚠️ :当响应的是 附带"身份凭证"的请求 时,
      • ❶ 服务端必须明确 允许源= 具体网址 Access-Control-Allow-Origin: 具体网址 ,而不能使用通配符"*"。
      • ❷ 服务器要明确 允许凭证 Access-Control-Allow-Credentials: true 标头,否则,浏览器会拒绝浏览器的 这个响应,不会把这个响应提供给调用的网页。
css 复制代码
		HTTP/1.1 200 OK
		Date: Mon, 01 Dec 2008 00:23:53 GMT
		Server: Apache/2
		Access-Control-Allow-Origin: *
		Keep-Alive: timeout=2, max=100
		Connection: Keep-Alive
		Transfer-Encoding: chunked
		Content-Type: application/xml
		
		[...XML Data...]
  • 知识拓展:请求附带"身份凭证"时(通常是 Cookie)、服务器响应里的通配符 一般不能用
    • ⚠️ 针对 附带身份凭证的请求时,响应标头里的 访问控制不能使用 通配符* ,要使用针对该源的 特定值
    • 服务器不能允许源 Access-Control-Allow-Origin 的值设为通配符(*
      • 而应将其设置为 特定的域 ,如:Access-Control-Allow-Origin: https://example.com
    • 服务器不能允许头 Access-Control-Allow-Headers 的值设为通配符(*
      • 而应将其设置为 特定标头名称的列表 ,如:Access-Control-Allow-Headers: X-PINGOTHER, Content-Type
    • 服务器不能允许方法 Access-Control-Allow-Methods 的值设为通配符(*
      • 而应将其设置为 特定请求方法名称的列表 ,如:Access-Control-Allow-Methods: POST, GET
    • 服务器不能允许的非普通头 Access-Control-Expose-Headers 的值设为通配符(*
      • 而应将其设置为 特定标头名称的列表 ,如:Access-Control-Expose-Headers: Content-Encoding, Kuma-Revision


⑴ 浏览器 请求控制 (默认:允许同源请求、拒绝跨源请求)

浏览器 默认的请求控制 :✔️ 默认:允许同源请求、拒绝跨源请求

  • ❶ 同源请求⇒ 总是被浏览器允许,同源能确保基本的安全性。
  • ❷ 跨源请求⇒ 浏览器默认"阻止跨源请求"。跨源请求 由 CORS 相关的 HTTP 头信息控制(服务器上的设置,会指示浏览器是否允许跨源请求)。

浏览器的"同源策略" :浏览器默认阻止 "跨域请求" ,这是浏览器的安全策略,而非 HTTP 协议本身的限制。

  • 浏览器限制 脚本内发起的 跨源 HTTP 请求。
    • XMLHttpRequestfetch() 遵循同源策略 ,使用这些 API 的网页,只能对相同的来源 请求资源,除非跨源的响应里 包含正确的 CORS 头部,允许跨源访问。


⑵ 浏览器的 两种跨源请求(简单请求、非简单请求)

  • 浏览器把跨源请求 划分为两类简单请求非简单请求
    • :不管是哪类请求,最终都由浏览器自行判断,跨源请求也都是 浏览器自动完成。
  • 两种请求的区别
    • 发送的 请求 HTTP 头信息 会不同。
    • 请求步骤不同
      • a. 简单请求 :会直接发送 实际请求 ,因为是跨源的,会在头信息之中,增加一个Origin字段 表明 请求的来源地址 ,服务器根据这个地址决定 要不要同意请求。
        • 请求来源地址 不在允许范围内 :浏览器响应里 会没有 允许源 Access-Control-Allow-Origin字段 ,浏览器识别后 抛出错误,被 XMLHttpRequestonerror 回调函数捕获。这种错误,状态码可能无法判断,因为状态码可能是 200(请求成功)。
        • 请求来源地址 在允许范围内 : 服务器响应会多出几个 CORS 相关的头信息,表明 允许的信息
      • b. 非简单请求 : 在发送实际请求前,会先发一个"预检请求"OPTIONS 请求),看看服务器同意不同意该请求。如果同意了,再发实际请求。

  • 简单请求 需满足条件
    • 请求方法GETPOSTHEAD 之一。
    • HTTP 头信息
      • Accept、Accept-Language、Content-Language
      • Range (仅使用单一区间头值。如, bytes=256- 或 bytes=127-255 )
      • Content-Type 值仅限于下列三者之一:text/plain、multipart/form-data、application/x-www-form-urlencoded (这也是 表单 <form> 标签中支持的 编码类型enctype 属性值。)
    • ❸ 如果请求是使用 XMLHttpRequest 对象发出的,在返回的 XMLHttpRequest.upload 对象属性上没有注册任何事件监听器 ;也就是说,给定一个 XMLHttpRequest 实例 xhr,没有调用 xhr.upload.addEventListener(),以监听该上传请求。
    • ❹ 请求中没有使用 ReadableStream 对象。


⑶ 跨源请求 CORS 相关的 HTTP 头信息 (表格)

跨源请求的 HTTP 请求头

  • 包括:请求来源地址、请求方法、请求头。
  • 共同点 :除了 请求来源地址Origin,其他都是以 访问控制 请求-* 开头Access-Control-Request-*
用途 HTTP 请求头字段 允许值
1.请求来源地址 。 ✔️ 跨源请求时:Origin 标头字段总是被发送。 Origin Origin 值为源站 URL 。 它不包含 任何路径信息,只是服务器名称。
2. 请求方法CORS 请求会用到哪些 HTTP 方法。 用于:预检请求。 Access-Control-Request-Method 值为 HTTP 方法。
3. 请求头 。 实际请求 所携带的标头字段。 用于:预检请求。 Access-Control-Request-Headers 标头名。 多个值,逗号分隔。

跨源请求的 HTTP 响应头 :服务器为跨源请求 返回的 HTTP 响应头

  • 包括:允许源、允许方法、允许标头、指定的允许标头、最大缓存时间/有效期、是否允许带身份凭证。
  • 共同点
    • 基本上都是以 访问控制 允许-*开头Access-Control-Allow-*
    • 只有两个以 访问控制 -*开头Access-Control-*Access-Control-Expose-HeadersAccess-Control-Max-Age
用途 HTTP 响应头字段 允许值
1. 允许源 :允许哪些源 访问服务器资源。 ✔️ 跨源访问时,每次回应 都必定包含的字段。 Access-Control-Allow-Origin `Access-Control-Allow-Origin: <origin>
2. 指定的 允许标头 Access-Control-Expose-Headers Access-Control-Expose-Headers: <header-name>[, <header-name>]* 多个值 逗号分隔。 在跨源访问时, XMLHttpRequest 对象的 getResponseHeader() 方法 只能拿到 6 个最基本的响应头Cache-ControlContent-LanguageContent-TypeExpiresLast-ModifiedPragma, 如果要访问其他头,则需要服务器设置本响应头。
3. 最大缓存时间/ 有效期(预检的响应结果) 。 用于:预检请求的响应。 Access-Control-Max-Age Access-Control-Max-Age: <delta-seconds>,以秒为单位。 指定本次预检请求的 有效期 ,单位为秒。 在此期间,不用发出另一条预检请求。
4.允许的请求方法 。 用于:预检请求的响应。 Access-Control-Allow-Methods 支持的 HTTP 方法名。字段必需,多个方法 逗号分隔 。 表明服务器支持的所有跨域请求的方法。 注意,返回的是所有支持的方法 ,而不单是浏览器请求的那个方法。 避免多次"预检"请求。
5. 实际请求中 允许携带的标头字段 可以 作为响应的一部分 被暴露的头部。 Access-Control-Allow-Headers 标头名 ,多个值 用逗号分隔。 表明服务器支持的 所有头信息字段 ,不限于浏览器在"预检"中请求的字段。 如果浏览器请求包括 Access-Control-Request-Headers 字段,则 Access-Control-Allow-Headers 字段是必需的响应。
6. 实际的请求 是否可以使用身份凭证 credentials美/krəˈdenʃ(ə)lz/。 用于:预检请求的响应。 Access-Control-Allow-Credentials Access-Control-Allow-Credentials: true :此标头的有效值仅为 true(区分大小写)。 如果不需要凭据,完全省略此标头(而不是将其值设置为 false)。 CORS 请求 默认不发送 Cookie 和 HTTP 认证信息。 Cookie 遵循同源政策。 如果要把 Cookie 发到跨源服务器,必须进行两个设置 : ❶ 开发者必须在 AJAX 请求中打开 withCredentials 属性。 var xhr = new XMLHttpRequest(); xhr.withCredentials = true; ❷ 服务器要设置 Access-Control-Allow-Credentials: true


⑷ 带预检请求 OPTIONS 的 跨源请求流程(非简单请求、浏览器自动发起)

  • CORS 预检请求 :用于检查服务器 是否支持 CORS 协议,并且 是否允许使用 特定的请求方法、请求标头、访问来源网址

  • 预检请求(OPTIONS 请求) 使用的 HTTP 请求标头

    • ❶ 告知服务器 实际请求所使用的 HTTP 方法访问控制 请求方法 Access-Control-Request-Method 标头。
    • ❷ 告知服务器 实际请求所携带的 标头访问控制 请求头 Access-Control-Request-Headers 标头。
    • ❸ 告知服务器 请求 来源网址 (可选):来源网址 Origin 标头 。
  • 浏览器 "自动发出 预检请求"

    • 当有必要的时候,浏览器会自动发出预检请求;所以在正常情况下,前端开发者不需要自己去发这样的请求,知道请求是怎样的即可。预检请求会在请求被标记为"需要预检"时进行,而对于简单请求 则不会进行 预检请求

  • : 浏览器预检请求 和 跨源服务器的响应的 简单示例。

    • 预检请求中:包括 Access-Control-Request-*Origin,而通过预检请求后, 实际请求中 只额外添加了 Origin
  • 第一步 ,js 代码中 有一个需要执行预检请求的 HTTP 请求。

    • 使用 POST 请求发送一个 XML 请求体,该请求包含了一个非标准的 HTTP X-PINGOTHER 请求标头。该请求的 Content-Typeapplication/xml,且使用了自定义的请求标头,所以,该请求是非简单请求,需要首先发起"预检请求"。 (这个预检请求过程, 是浏览器自动判定和发起)
    • 这样的请求标头并不是 HTTP/1.1 的一部分,但通常对于 web 应用很有用处。
javascript 复制代码
		const fetchPromise = fetch("https://bar.other/doc", {
		  method: "POST",
		  mode: "cors",
		  headers: {
		    "Content-Type": "text/xml",
		    "X-PINGOTHER": "pingpong",
		  },
		  body: "<person><name>Arun</name></person>",
		});
		
		fetchPromise.then((response) => {
		  console.log(response.status);
		});
  • 第二步:浏览器判断该请求 需要预检请求,自动发起预检请求(OPTIONS 请求)
    • 服务器根据 请求方法 Access-Control-Request-Method、请求头Access-Control-Request-Headers决定,该实际请求是否被允许。
css 复制代码
		OPTIONS /doc HTTP/1.1
		Host: bar.other
		User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:71.0) Gecko/20100101 Firefox/71.0
		Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
		Accept-Language: en-us,en;q=0.5
		Accept-Encoding: gzip,deflate
		Connection: keep-alive
		Origin: https://foo.example
		Access-Control-Request-Method: POST
		Access-Control-Request-Headers: X-PINGOTHER, Content-Type
  • 第三步,服务器的响应 (针对 预检请求)。
    • 服务器 允许的源 :访问控制 允许源 Access-Control-Allow-Origin
      • 允许源-具体值 和 vary允许源是具体网址时,Vary 字段的值 必须包含 Origin
        • 如果服务端指定了具体的单个源(作为允许列表的一部分,可能会根据请求的来源而动态改变)而非通配符(*),那么响应标头中的 Vary 字段的值 必须包含 Origin
        • ⇒ 告诉客户端:服务器 对不同的 Origin ,返回不同的内容Vary: Accept-Encoding, Origin
    • 服务器 允许的 HTTP 方法 :访问控制 允许方法 Access-Control-Allow-Methods
    • 服务器 允许的请求头 :访问控制 允许头 Access-Control-Allow-Headers
    • 服务器 响应结果最大缓存时间/ 有效期 :访问控制缓存最大时间 Access-Control-Max-Age
      • 缓存响应结果,是为了在同一个 URL 下创建的请求中使用。为了缓存预检响应,浏览器使用一个特定的缓存,这个缓存 是与一般 HTTP 缓存分开的(浏览器管理的)。
      • 单位为秒,默认值是 5 秒。:浏览器自身维护了一个最大有效时间,如果该标头字段的值超过了最大有效时间,将不会生效。
css 复制代码
		HTTP/1.1 204 No Content
		Date: Mon, 01 Dec 2008 01:15:39 GMT
		Server: Apache/2
		Access-Control-Allow-Origin: https://foo.example
		Access-Control-Allow-Methods: POST, GET, OPTIONS
		Access-Control-Allow-Headers: X-PINGOTHER, Content-Type
		Access-Control-Max-Age: 86400
		Vary: Accept-Encoding, Origin
		Keep-Alive: timeout=2, max=100
		Connection: Keep-Alive
  • 第四步 ,通过了预检请求,被允许的话,浏览器发送 实际请求
css 复制代码
		POST /doc HTTP/1.1
		Host: bar.other
		User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:71.0) Gecko/20100101 Firefox/71.0
		Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
		Accept-Language: en-us,en;q=0.5
		Accept-Encoding: gzip,deflate
		Connection: keep-alive
		X-PINGOTHER: pingpong
		Content-Type: text/xml; charset=UTF-8
		Referer: https://foo.example/examples/preflightInvocation.html
		Content-Length: 55
		Origin: https://foo.example
		Pragma: no-cache
		Cache-Control: no-cache
		
		<person><name>Arun</name></person>
  • 第五步 ,针对实际请求,服务器给出响应。
    • 一旦服务器通过了"预检"请求,在预检的有效期内(Access-Control-Max-Age),以后每次浏览器 CORS 跨源请求,就都跟简单请求一样,只会有一个 Origin 头信息字段。服务器的回应,也都会有一个Access-Control-Allow-Origin头信息字段。
css 复制代码
	HTTP/1.1 200 OK
	Date: Mon, 01 Dec 2008 01:15:40 GMT
	Server: Apache/2
	Access-Control-Allow-Origin: https://foo.example
	Vary: Accept-Encoding, Origin
	Content-Encoding: gzip
	Content-Length: 235
	Keep-Alive: timeout=2, max=99
	Connection: Keep-Alive
	Content-Type: text/plain
	
	[Some XML payload]


♣ 结束语 和 友情链接

  • 快速搜索 :涉及知识点比较多,看过之后忘了很正常,想查询时,请按 Ctrl+F 快速搜索关键字哦 (* ̄︶ ̄)。
    • 搜索后,多次按下 enter 键 或 点击搜索框后的 上下箭头,就能在关键词之间快速跳转。

  • 友情链接
    • MDN Web Docs
      • 在搜索框输入查询,可以看详细知识点,也可以文章的最下方看到浏览器支持,点击浏览器版本,还能看到具体支持情况;
    • Can I use... Support tables for HTML5, CSS3, etc
      • 在搜索框输入查询,可以查询特性的浏览器支持,点击浏览器版本,还能看到具体支持情况;

  • 感谢:❤ 如果这篇文章 对您有帮助的话,可以点赞、评论、关注,鼓励下作者哦,谢谢小可爱们 ~╮( ̄▽ ̄)╭ ~
  • 欢迎指正: 如果发现 有需要更正的细节问题,欢迎指正。
  • 喜欢请收藏:喜欢本文 可收藏哦,方便快捷,会持续进行 ❶ 知识点更新 及 ❷ 修正错误。

  • 转载 请评论告知作者 并注明出处 ,Thanks♪(・ω・)ノ
    • 作者:Hey_Coder
    • 来源:CSDN
    • 版权声明:本文为博主原创文章,转载请附上博文链接!
相关推荐
灰子学技术1 天前
Envoy HTTP 过滤器处理技术文档
网络·网络协议·http
时空自由民.2 天前
HTTP协议和HTTPS协议结合天气获取案例介绍
网络协议·http·https
时空自由民.2 天前
HTTP协议帧格式
网络·网络协议·http
Rust研习社2 天前
使用 Axum 构建高性能异步 Web 服务
开发语言·前端·网络·后端·http·rust
灰子学技术2 天前
Envoy HTTP 流量层面的 Metric 指标分析
网络·网络协议·http
TimeAground2 天前
HTTP 协议全解:从报文到 HTTP/3,Android 开发者需要知道的一切
http
lifewange2 天前
如何设计一个 RESTful API
后端·http·restful
夜瞬3 天前
HTTP基础教程:请求方法、状态码、JSON、鉴权、超时、重试与流式返回
网络协议·http·json