目录

一、理论概述

1. 什么是跨域请求(Cross-domain Request)

2. 浏览器的同源策略(Same-origin Policy)

3. 存在的安全风险

4. CSRF攻击简介

二、跨域方案

1. 跨域资源共享(CORS)

(1)CORS简单请求

(2)CORS非简单请求(也称预检请求)

HTTP 响应首部字段

Access-Control-Allow-Origin

Access-Control-Expose-Headers

Access-Control-Max-Age

Access-Control-Allow-Credentials

Access-Control-Allow-Methods

Access-Control-Allow-Headers

HTTP 请求首部字段

Origin

Access-Control-Request-Method

Access-Control-Request-Headers

规范

浏览器兼容性

2. JSONP请求

3. JSONP和CORS请求比较

4.子域跨父域

5. iframe跨父窗口

6. window.postMessage

7. 使用代理避开跨域请求


一、理论概述

1. 什么是跨域请求(Cross-domain Request)

​        在 HTML 中,<a>, <form>, <img>, <script>, <iframe>, <link> 等标签以及 Ajax 都可以指向一个资源地址,而所谓的跨域请求就是指:当前发起请求的域与该请求指向的资源所在的域不同时的请求。这里的域指的是这样的一个概念:我们认为如果 “协议 + 域名 + 端口号” 均相同,那么就是同域。

举个例子:假如一个域名为 aaa.cn的网站,它发起一个资源路径为 aaa.cn/books/getBookInfo的 Ajax 请求,那么这个请求是同域的,因为资源路径的协议、域名以及端口号与当前域一致(例子中协议名默认为http,端口号默认为80)。但是,如果发起一个资源路径为 bbb.com/pay/purchase的 Ajax 请求,因为请求域 http://bbb.com:80和发起请求的域 http://aaa.cn:80不同,那么这个请求就是跨域请求。

2. 浏览器的同源策略(Same-origin Policy)

同源策略(Same origin policy)是一种约定,它是浏览器最核心也是最基本的安全功能。出于安全考虑,浏览器限制从JS脚本发起的跨源HTTP请求。 例如,XMLHttpRequest和Fetch API都遵循同源策略。

  • 域名、协议、端口有一个不同就不是同源,三者均相同,这两个网站才是同源
  • DOM 层面的同源策略:限制了来自不同源的”Document”对象或 JS 脚本,对当前“document”对象的读取或设置某些属性
  • Cookie和XMLHttprequest层面的同源策略:禁止 Ajax 直接发起跨域HTTP请求(其实可以发送请求,结果被浏览器拦截,不展示),同时 Ajax 请求不能携带与本网站不同源的 Cookie。
  • 同源策略的非绝对性:<script><img><iframe><link><video><audio>等带有src属性的标签可以从不同的域加载和执行资源。
  • 其他插件的同源策略:flash、java applet、silverlight、googlegears等浏览器加载的第三方插件也有各自的同源策略,只是这些同源策略不属于浏览器原生的同源策略,如果有漏洞则可能被黑客利用,从而留下XSS攻击的后患

因为同源策略的限制,不同协议(http、https)、域名或者端口无法直接进行JS ajax请求。 同源策略只针对于浏览器端,浏览器一旦检测到请求的结果的域名不一致后,会堵塞请求结果。这里注意,跨域请求是可以发去的,但是请求响应response被浏览器堵塞了

那么问题又来了,如果恶意网站使用这个方法伪造一个转账请求,是不是就可以得逞了呢?答案是否定的。因为同源策略限制了不同源脚本之间的访问,也就是说恶意js脚本无法获取用户在银行网站的Cookie(网站身份验证的token会短期存放在Session中或较长期存放在Cookie中),而实际上在JS跨域请求时,浏览器既不会带跨域网站的cookie,也不会带上恶意网站的cookie。

另外,浏览器为什么仅仅限制读(即浏览器拦截请求结果)呢?一方面如果访问的是黑网站,那么网站无法跟据请求结果继续下一步的操作,如不断地猜测密码;另一方面如果访问的是白网站,block掉请求结果,应该是考虑到了请求结果可能会使得页面重定向,或者是给网页添加一个恶意的iframe之类的。

3. 存在的安全风险

由于同源策略的限制,跨域的ajax请求不会带上cookie,然而 script / iframe / img 等标签却是支持跨域的,所以在请求的时候是会带上cookie的。如果登陆了Abank.com,那么cookie里面就有了tocken,同时又打开了另外一个标签页访问了evil.com,而这个页面中有如下一个iframe, 这个iframe的src是一个指向Abank.com的转账请求,如果Abank.com的转账请求没有第二重加密措施的话,那么请求转账就成功了!

<iframe src="http://Abank.com/app/transferFunds?amount=1500&destinationAccount=... >

第二个例子是路由器的配置,假设我在网上找到了一个路由器配置教程的网站。这个网站里面偷偷地加一个img标签:

<img src=”http://192.168.1.1/admin/config/outsideInterface?nexthop=123.45.67.89” alt=”pwned” height=”1” width=”1”/>

其中192.168.1.1是很多路由器的配置地址。这个1像素的图片没加载出来被忽略了,但是它的请求却发出去了。这个请求给路由器添加了一个vpn代理,指向黑客的代理服务器。如果路由器也是把登陆验证放在cookie里面,那么这个设置vpn的请求很可能就成功了,以后的连接路由器的每个请求都会先经过黑客的服务。

到这里,很明显一个防CSRF攻击的策略就是将token添加到请求的参数里面,也就是说每个需要验证身份的请求都要显式地带上token值(因为不能跨域读取token,只能利用img等标签的自动带cookie漏洞)。详见:Cross-Site Request Forgery Guide: Learn All About CSRF Attacks and CSRF Protection

4. CSRF攻击简介

CSRF(Cross-site request forgery),中文名称:跨站请求伪造,也被称为:one click attack/session riding,缩写为:CSRF/XSRF。CSRF攻击者在用户已经登录目标网站之后,诱使用户访问一个攻击页面,利用目标网站对用户的信任,以用户身份在攻击页面对目标网站发起伪造用户操作的请求,达到攻击目的。

​         CSRF 攻击的原理大致描述如下:有两个网站,其中A网站是真实受信任的网站,而B网站是危险网站。在用户登陆了受信任的A网站是,本地会存储A网站相关的Cookie,并且浏览器也维护这一个Session会话。这时,如果用户在没有登出A网站的情况下访问危险网站B,那么危险网站B就可以模拟发出一个对A网站的请求(跨域请求)对A网站进行操作,而在A网站的角度来看是并不知道请求是由B网站发出来的(Session和Cookie均为A网站的),这时便成功发动一次CSRF 攻击。

​      因而 CSRF 攻击可以简单理解为:攻击者盗用了你的身份,以你的名义发送请求。CSRF能够做的事情包括:以你的名义发送邮件,发消息,盗取你的账号,甚至于购买商品,虚拟货币转账......造成的问题包括:个人隐私泄露以及财产安全。

​       因此,大多数浏览器都会对跨域请求作出限制,这是从浏览器层面上的对 CSRF 攻击的一种防御,但是需要注意的是在复杂的网络环境中借助浏览器来防御 CSRF 攻击并不足够,还需要从服务端或者客户端方面入手防御,详细可以参考这篇文章浅谈CSRF攻击方式。

二、跨域方案

虽然在安全层面上同源限制是必要的,但有时同源策略会对我们的合理用途造成影响,为了避免开发的应用受到限制,有多种方式可以绕开同源策略,下面注重介绍的是经常使用的 JSONP, CORS 方法。

1. 跨域资源共享(CORS)

跨源资源共享 Cross-Origin Resource Sharing (CORS) 是一个新的 W3C 标准,它新增的一组HTTP首部字段,允许服务端其声明哪些源站有权限访问哪些资源。换言之,它允许浏览器向声明了 CORS 的跨域服务器,发出 XMLHttpReuest 请求,从而克服 Ajax 只能同源使用的限制。

另外,规范要求,对那些可能对服务器数据产生副作用的 HTTP 请求方法(特别是 GET 以外的 HTTP 请求,或者搭配某些 MIME 类型的 POST 请求),浏览器必须首先使用 OPTIONS 方法发起一个预检请求(preflight request),从而获知服务端是否允许该跨域请求。服务器确认允许之后,才发起实际的 HTTP 请求。在预检请求的返回中,服务器端也可以通知客户端,是否需要携带身份凭证(包括 Cookies 和 HTTP 认证相关数据)。

CORS需要浏览器和服务器同时支持。目前,所有浏览器都支持该功能,IE浏览器不能低于IE10。

CORS请求失败会产生错误,但是为了安全,在JavaScript代码层面是无法获知到底具体是哪里出了问题。你只能查看浏览器的控制台以得知具体是哪里出现了错误。

浏览器将CORS请求分成两类:简单请求(simple request)和非简单请求(not-so-simple request)。

(1)CORS简单请求

某些请求不会触发 CORS 预检请求,本文称这样的请求为“简单请求”。请注意,该术语并不属于 Fetch (其中定义了 CORS)规范。若请求满足所有下述条件(参考mozilla浏览器 https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Access_control_CORS#%E7%AE%80%E5%8D%95%E8%AF%B7%E6%B1%82),则该请求可视为“简单请求”:

  • 使用下列方法之一:

    • GET
    • HEAD
    • POST
  • Fetch 规范定义了对 CORS 安全的首部字段集合,不得人为设置该集合之外的其他首部字段。该集合为:
    • Accept
    • Accept-Language
    • Content-Language
    • Content-Type (需要注意额外的限制)
    • DPR
    • Downlink
    • Save-Data
    • Viewport-Width
    • Width
  • Content-Type 的值仅限于下列三者之一:
    • text/plain
    • multipart/form-data
    • application/x-www-form-urlencoded
  • 请求中的任意 XMLHttpRequestUpload 对象均没有注册任何事件监听器;XMLHttpRequestUpload 对象可以使用 XMLHttpRequest.upload 属性访问。
  • 请求中没有使用 ReadableStream 对象。

比如说,假如站点 http://foo.example 的网页应用想要访问 http://bar.other 的资源。http://foo.example 的网页中可能包含类似于下面的 JavaScript 代码:

var invocation = new XMLHttpRequest();
var url = 'http://bar.other/resources/public-data/';function callOtherDomain() {if(invocation) {    invocation.open('GET', url, true);invocation.onreadystatechange = handler;invocation.send(); }
}

客户端和服务器之间使用 CORS 首部字段来处理跨域权限:

分别检视请求报文和响应报文:

GET /resources/public-data/ HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.1b3pre) Gecko/20081130 Minefield/3.1b3pre
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
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Connection: keep-alive
Referer: http://foo.example/examples/access-control/simpleXSInvocation.html
Origin: http://foo.exampleHTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 00:23:53 GMT
Server: Apache/2.0.61
Access-Control-Allow-Origin: *
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Transfer-Encoding: chunked
Content-Type: application/xml[XML Data]

第 1~10 行是请求首部。第10行 的请求首部字段 Origin 表明该请求来源于 http://foo.exmaple

第 13~22 行是来自于 http://bar.other 的服务端响应。响应中携带了响应首部字段 Access-Control-Allow-Origin(第 16 行)。使用 Origin 和 Access-Control-Allow-Origin 就能完成最简单的访问控制。本例中,服务端返回的 Access-Control-Allow-Origin: * 表明,该资源可以被任意外域访问。如果服务端仅允许来自 http://foo.example 的访问,该首部字段的内容如下:

Access-Control-Allow-Origin: http://foo.example

现在,除了 http://foo.example,其它外域均不能访问该资源(该策略由请求首部中的 ORIGIN 字段定义,见第10行)。Access-Control-Allow-Origin 应当为 * 或者包含由 Origin 首部字段所指明的域名。

(2)CORS非简单请求(也称预检请求)

与前述简单请求不同,“需预检的请求”要求必须首先使用 OPTIONS   方法发起一个预检请求到服务器,以获知服务器是否允许该实际请求。"预检请求“的使用,可以避免跨域请求对服务器的用户数据产生未预期的影响。

当请求满足下述任一条件(mozilla浏览器 https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Access_control_CORS#%E9%A2%84%E6%A3%80%E8%AF%B7%E6%B1%82)时,即应首先发送预检请求:

  • 使用了下面任一 HTTP 方法:

    • PUT
    • DELETE
    • CONNECT
    • OPTIONS
    • TRACE
    • PATCH
  • 人为设置了对 CORS 安全的首部字段集合之外的其他首部字段。该集合为:
    • Accept
    • Accept-Language
    • Content-Language
    • Content-Type (需要注意额外的限制)
    • DPR
    • Downlink
    • Save-Data
    • Viewport-Width
    • Width
  • Content-Type 的值不属于下列之一:
    • application/x-www-form-urlencoded
    • multipart/form-data
    • text/plain
  • 请求中的XMLHttpRequestUpload 对象注册了任意多个事件监听器。
  • 请求中使用了ReadableStream对象。

注意: WebKit Nightly 和 Safari Technology Preview 为AcceptAccept-Language, 和 Content-Language 首部字段的值添加了额外的限制。如果这些首部字段的值是“非标准”的,WebKit/Safari 就不会将这些请求视为“简单请求”。WebKit/Safari 并没有在文档中列出哪些值是“非标准”的,不过我们可以在这里找到相关讨论:Require preflight for non-standard CORS-safelisted request headers Accept, Accept-Language, and Content-Language, Allow commas in Accept, Accept-Language, and Content-Language request headers for simple CORS, and Switch to a blacklist model for restricted Accept headers in simple CORS requests。其它浏览器并不支持这些额外的限制,因为它们不属于规范的一部分。

如下是一个需要执行预检请求的 HTTP 请求:

var invocation = new XMLHttpRequest();
var url = 'http://bar.other/resources/post-here/';
var body = '<?xml version="1.0"?><person><name>Arun</name></person>';function callOtherDomain(){if(invocation){invocation.open('POST', url, true);invocation.setRequestHeader('X-PINGOTHER', 'pingpong');invocation.setRequestHeader('Content-Type', 'application/xml');invocation.onreadystatechange = handler;invocation.send(body); }
}......

上面的代码使用 POST 请求发送一个 XML 文档,该请求包含了一个自定义的请求首部字段(X-PINGOTHER: pingpong)。另外,该请求的 Content-Type 为 application/xml。因此,该请求需要首先发起“预检请求”。

OPTIONS /resources/post-here/ HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.1b3pre) Gecko/20081130 Minefield/3.1b3pre
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
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Connection: keep-aliveOrigin: http://foo.example
Access-Control-Request-Method: POST
Access-Control-Request-Headers: X-PINGOTHER, Content-TypeHTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:15:39 GMT
Server: Apache/2.0.61 (Unix)
Access-Control-Allow-Origin: http://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
Content-Encoding: gzip
Content-Length: 0
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Content-Type: text/plain

预检请求完成之后,发送实际请求:

POST /resources/post-here/ HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.1b3pre) Gecko/20081130 Minefield/3.1b3pre
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
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Connection: keep-alive
X-PINGOTHER: pingpong
Content-Type: text/xml; charset=UTF-8
Referer: http://foo.example/examples/preflightInvocation.html
Content-Length: 55
Origin: http://foo.example
Pragma: no-cache
Cache-Control: no-cache<?xml version="1.0"?><person><name>Arun</name></person>HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:15:40 GMT
Server: Apache/2.0.61 (Unix)
Access-Control-Allow-Origin: http://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 GZIP'd payload]

浏览器检测到,从 JavaScript 中发起的请求需要被预检。从上面的报文中,我们看到,第 1~12 行发送了一个使用 OPTIONS 方法的“预检请求 OPTIONS 是 HTTP/1.1 协议中定义的方法,用以从服务器获取更多信息。该方法不会对服务器资源产生影响。 预检请求中同时携带了下面两个首部字段:

接下来的内容将讨论相关场景,并剖析该机制所涉及的 HTTP 首部字段。

Access-Control-Request-Method: POST
Access-Control-Request-Headers: X-PINGOTHER, Content-Type

  首部字段 Access-Control-Request-Method 告知服务器,实际请求将使用 POST 方法。首部字段 Access-Control-Request-Headers 告知服务器,实际请求将携带两个自定义请求首部字段:X-PINGOTHERContent-Type。服务器据此决定,该实际请求是否被允许。

第14~26 行为预检请求的响应,表明服务器将接受后续的实际请求。重点看第 17~20 行:

Access-Control-Allow-Origin: http://foo.example
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: X-PINGOTHER, Content-Type
Access-Control-Max-Age: 86400

首部字段 Access-Control-Allow-Methods 表明服务器允许客户端使用 POST, GET OPTIONS 方法发起请求。该字段与HTTP/1.1 Allow: response header类似,但仅限于在需要访问控制的场景中使用。

首部字段Access-Control-Allow-Headers 表明服务器允许请求中携带字段X-PINGOTHER  Content-Type Access-Control-Allow-Methods 一样,Access-Control-Allow-Headers的值为逗号分割的列表。

最后,首部字段 Access-Control-Max-Age 表明该响应的有效时间为 86400 秒,也就是 24 小时。在有效时间内,浏览器无须为同一请求再次发起预检请求。请注意,浏览器自身维护了一个最大有效时间,如果该首部字段的值超过了最大有效时间,将不会生效。

预检请求与重定向

大多数浏览器不支持针对于预检请求的重定向。如果一个预检请求发生了重定向,浏览器将报告错误:

The request was redirected to 'https://example.com/foo', which is disallowed for cross-origin requests that require preflight

Request requires preflight, which is disallowed to follow cross-origin redirect

CORS 最初要求该行为,不过在后续的修订中废弃了这一要求。

在浏览器的实现跟上规范之前,有两种方式规避上述报错行为:

  • 在服务端去掉对预检请求的重定向;
  • 将实际请求变成一个简单请求。

如果上面两种方式难以做到,我们仍有其他办法:

  • 发出一个简单请求(使用  Response.url 或 XHR.responseURL)以判断真正的预检请求会返回什么地址。
  • 发出另一个请求(真正的请求),使用在上一步通过Response.url 或 XMLHttpRequest.responseURL获得的URL。

不过,如果请求是由于存在 Authorization 字段而引发了预检请求,则这一方法将无法使用。这种情况只能由服务端进行更改。

附带身份凭证的请求

Fetch 与 CORS 的一个有趣的特性是,可以基于  HTTP cookies 和 HTTP 认证信息发送身份凭证。一般而言,对于跨域 XMLHttpRequest 或 Fetch 请求,浏览器不会发送身份凭证信息。如果要发送凭证信息,需要设置 XMLHttpRequest 的某个特殊标志位。

本例中,http://foo.example 的某脚本向 http://bar.other 发起一个GET 请求,并设置 Cookies:

var invocation = new XMLHttpRequest();
var url = 'http://bar.other/resources/credentialed-content/';function callOtherDomain(){if(invocation) {invocation.open('GET', url, true);invocation.withCredentials = true;invocation.onreadystatechange = handler;invocation.send(); }
}

第 7 行将 XMLHttpRequest 的 withCredentials 标志设置为 true,从而向服务器发送 Cookies。因为这是一个简单 GET 请求,所以浏览器不会对其发起“预检请求”。但是,如果服务器端的响应中未携带 Access-Control-Allow-Credentials: true ,浏览器将不会把响应内容返回给请求的发送者。

客户端与服务器端交互示例如下:

GET /resources/access-control-with-credentials/ HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.1b3pre) Gecko/20081130 Minefield/3.1b3pre
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
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Connection: keep-alive
Referer: http://foo.example/examples/credential.html
Origin: http://foo.example
Cookie: pageAccess=2HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:34:52 GMT
Server: Apache/2.0.61 (Unix) PHP/4.4.7 mod_ssl/2.0.61 OpenSSL/0.9.7e mod_fastcgi/2.4.2 DAV/2 SVN/1.4.2
X-Powered-By: PHP/5.2.6
Access-Control-Allow-Origin: http://foo.example
Access-Control-Allow-Credentials: true
Cache-Control: no-cache
Pragma: no-cache
Set-Cookie: pageAccess=3; expires=Wed, 31-Dec-2008 01:34:53 GMT
Vary: Accept-Encoding, Origin
Content-Encoding: gzip
Content-Length: 106
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Content-Type: text/plain[text/plain payload]

即使第 11 行指定了 Cookie 的相关信息,但是,如果 bar.other 的响应中缺失 Access-Control-Allow-Credentials: true(第 19 行),则响应内容不会返回给请求的发起者。

附带身份凭证的请求与通配符

对于附带身份凭证的请求,服务器不得设置 Access-Control-Allow-Origin 的值为“*”。

这是因为请求的首部中携带了 Cookie 信息,如果 Access-Control-Allow-Origin 的值为“*”,请求将会失败。而将 Access-Control-Allow-Origin 的值设置为 http://foo.example,则请求将成功执行。

另外,响应首部中也携带了 Set-Cookie 字段,尝试对 Cookie 进行修改。如果操作失败,将会抛出异常。

HTTP 响应首部字段

本节列出了规范所定义的响应首部字段。上一小节中,我们已经看到了这些首部字段在实际场景中是如何工作的。

Access-Control-Allow-Origin

响应首部中可以携带一个 Access-Control-Allow-Origin 字段,其语法如下:

Access-Control-Allow-Origin: <origin> | *

其中,origin 参数的值指定了允许访问该资源的外域 URI。对于不需要携带身份凭证的请求,服务器可以指定该字段的值为通配符,表示允许来自所有域的请求。

例如,下面的字段值将允许来自 http://mozilla.com 的请求:

Access-Control-Allow-Origin: http://mozilla.com

如果服务端指定了具体的域名而非“*”,那么响应首部中的 Vary 字段的值必须包含 Origin。这将告诉客户端:服务器对不同的源站返回不同的内容。

Access-Control-Expose-Headers

译者注:在跨域访问时,XMLHttpRequest对象的getResponseHeader()方法只能拿到一些最基本的响应头,Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma,如果要访问其他头,则需要服务器设置本响应头。

Access-Control-Expose-Headers 头让服务器把允许浏览器访问的头放入白名单,例如:

Access-Control-Expose-Headers: X-My-Custom-Header, X-Another-Custom-Header

这样浏览器就能够通过getResponseHeader访问X-My-Custom-Header和 X-Another-Custom-Header 响应头了

Access-Control-Max-Age

Access-Control-Max-Age 头指定了preflight请求的结果能够被缓存多久,请参考本文在前面提到的preflight例子。

Access-Control-Max-Age: <delta-seconds>

delta-seconds 参数表示preflight请求的结果在多少秒内有效。

Access-Control-Allow-Credentials

Access-Control-Allow-Credentials 头指定了当浏览器的credentials设置为true时是否允许浏览器读取response的内容。当用在对preflight预检测请求的响应中时,它指定了实际的请求是否可以使用credentials。请注意:简单 GET 请求不会被预检;如果对此类请求的响应中不包含该字段,这个响应将被忽略掉,并且浏览器也不会将相应内容返回给网页。

Access-Control-Allow-Credentials: true

上文已经讨论了附带身份凭证的请求。

Access-Control-Allow-Methods

Access-Control-Allow-Methods 首部字段用于预检请求的响应。其指明了实际请求所允许使用的 HTTP 方法。

Access-Control-Allow-Methods: <method>[, <method>]*

相关示例见这里。

Access-Control-Allow-Headers

Access-Control-Allow-Headers 首部字段用于预检请求的响应。其指明了实际请求中允许携带的首部字段。

Access-Control-Allow-Headers: <field-name>[, <field-name>]*

HTTP 请求首部字段 

本节列出了可用于发起跨域请求的首部字段。请注意,这些首部字段无须手动设置。 当开发者使用 XMLHttpRequest 对象发起跨域请求时,它们已经被设置就绪。

Origin

Origin 首部字段表明预检请求或实际请求的源站。

Origin: <origin>

origin 参数的值为源站 URI。它不包含任何路径信息,只是服务器名称。

Note: 有时候将该字段的值设置为空字符串是有用的,例如,当源站是一个 data URL 时。

注意,不管是否为跨域请求,ORIGIN 字段总是被发送。

Access-Control-Request-Method

Access-Control-Request-Method 首部字段用于预检请求。其作用是,将实际请求所使用的 HTTP 方法告诉服务器。

Access-Control-Request-Method: <method>

相关示例见这里。

Access-Control-Request-Headers

Access-Control-Request-Headers 首部字段用于预检请求。其作用是,将实际请求所携带的首部字段告诉服务器。

Access-Control-Request-Headers: <field-name>[, <field-name>]*

相关示例见这里。

规范

Specification Status Comment
Fetch
CORS
Living Standard New definition; supplants CORS specification.
Unknown Unknown Initial definition.

浏览器兼容性

Update compatibility data on GitHub

                                                    Desktop                                                     Mobile
  Chrome Edge Firefox Internet Explorer Opera Safari Android webview Chrome for Android Firefox for Android Opera for Android Safari on iOS Samsung Internet
Access-Control-Allow-Origin      4   12     3.5       10    12    4       2    YES      4      12    3.2     YES

  • IE 10 提供了对规范的完整支持,但在较早版本(8 和 9)中,CORS 机制是借由 XDomainRequest 对象完成的。
  • Firefox 3.5 引入了对 XMLHttpRequests 和 Web 字体的跨域支持(但最初的实现并不完整,这在后续版本中得到完善);Firefox 7 引入了对 WebGL 贴图的跨域支持;Firefox 9 引入了对 drawImage 的跨域支持。

2. JSONP请求

JSON的核心是:动态添加<script>标签来调用服务器提供的js脚本。JQuery ajax本质上是通过script标签实现跨域请求,而非真正的ajax请求。整个过程可简单描述为:客户端跨域请求数据responseData,然后使用客户端js函数handleResponse(responseData)对数据进行处理。具体实现思路为:客户端封装一个脚本请求如<script src="https://www.jsonp_request.com/request_method?callback=handleResponse&param1=valu1...">,其中handleResponse为本地JS函数;然后,服务器端获取callback的回调函数名即handleResponse后,将处理结果responseData,包装成javascript函数调用的形式,即字符串“handleResponse({responseData的json数据})”传回客户端;最后,客户端使用eval(返回字符串);执行返回结果。

JQuery封装JSONP请求:对于经常使用jQuery的开发者来说,肯定注意到jQuery封装的$.ajax中有一个dataType属性,如果将该属性设置成dataType:"jsonp",就能实现JSONP跨域了。

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>jQuery实现JSONP</title>
</head>
<body><div id="mydiv"><button id="btn">点击</button></div>
</body>
<script type="text/javascript" src="https://code.jquery.com/jquery-3.1.0.min.js"></script>
<script type="text/javascript">$(function(){$("#btn").click(function(){$.ajax({async : true,url : "https://api.douban.com/v2/book/search",type : "GET", //如果是跨域请求或将crossDomain设置为true,则type只能是GETdataType : "jsonp", // 返回的数据类型,设置为JSONP方式jsonp : 'callback', //指定一个查询参数名称来覆盖默认的 jsonp 回调参数名 callbackjsonpCallback: 'handleResponse', //设置回调函数名,默认jquery自定义data : {q : "javascript", count : 1}, success: function(response, status, xhr){ //返回数据,即response.dataconsole.log('状态为:' + status + ',状态是:' + xhr.statusText);console.log(response);}});});});
</script>

大家可能注意到,JQuery是怎么实现返回数据的?其实,在JQuery的JSONP实现中,首先将你在success中定义的函数封装成jsonpCallback定义的函数,然后再封装一个脚本请求如<script src="https://api.douban.com/v2/book/search?callback=handleResponse&q=javascript&count=1">。

3. JSONP和CORS请求比较

JSONP请求,缺点是仅支持GET请求,必须完全信任提供服务的第三方,优点是支持老式浏览器,兼容性较好。

CORS比JSONP更强大,支持所有类型的HTTP请求。

4.子域跨父域

子域跨父域是支持的,但是需要显式将子域的域名改成父域的,例如mail.mysite.com要请求mysite.com的数据,那么在mail.mysite.com脚本里需要执行:

document.domain = "mysite.com";

5. iframe跨父窗口

如果iframe与父窗口也有同源策略的限制,父域无法直接读取不同源的iframe的DOM内容以及监听事件,但是iframe可以调用父窗口提供的api。iframe通过window.parent得到父窗口的window对象,然后父窗口定义一个全局对象供iframe调用。

例如在页面通过iframe的方式嵌入一个youtobe的视频,如果需要手动播放视频、监听iframe的播放事件,页面需要引入youtobe的视频播放控制api,在这个js文件里面定义了一个全局对象YT:

if (!window['YT']) {var YT = {loading: 0,loaded: 0};}

而在视频iframe的脚本里通过window.parent获取得到父窗口即自己网站的页面:

sr = new Cq(window.parent, d, b)

自已网站的页面也是在这个YT对象自定义一些东西,如添加播放事件监听:

new YT.Player('video', { events:{ 'onStateChange': function(data){... } } });

6. window.postMessage

在上面第(5)点,父窗口无法向不同源的iframe传递东西,通过window.postMessage可以做到,父窗口向iframe传递一个消息,而iframe监听消息事件。

例如在8000端口的页面嵌入了一个9000端口的iframe:

<iframe src="http://server.com:9000"></iframe>

然后9000端口post一个message:

window.onload = function(){window.frames[0].postMessage("hello, this is from http://localhost:8000/", "http://server.com:9000/");}

postMessage执行的上下文必须是接收信息的window,传递两个参数,第一个是数据,第二个是目标窗口。

同时,iframe即9000端口的页面监听message事件:

  window.addEventListener("message", receiveMessage);function receiveMessage(event){var origin = event.origin || event.originalEvent.origin; //身份验证if (origin !== "http://localhost:8000"){return;}console.log("receiveMessage: " + event.data); }

这样子iframe就可收到父窗口的信息了:

同理iframe也可以向父窗口发送消息:

window.parent.postMessage("hello, this is from http://server.com:9000", "http://localhost:8000");

父窗口收到:

window.postMessage也适用于通过window.open打开的子窗口,方法类似。

补充一点,如果iframe与父窗口是同源的,则父窗口可以直接获取到iframe的内容,这个方法常用于无刷新上传文件。

7. 使用代理避开跨域请求

例如www.a.com/index.html页面去调用www.b.com/service.jsp,可以通过写一个接口www.a.com/service.jsp,由这个接口在后端去调用www.b.com/service.jsp并取到返回值,然后再返回给index.html。


参考链接:

1. 什么是跨域请求以及实现跨域的方案

2. Access-Control-Allow- 设置 跨域资源共享 CORS 详解

3. HTTP访问控制(CORS)

4. 同源策略和跨域请求研究

5. Jsonp解决跨域的原理及过程

Web跨域请求及其解决方案相关推荐

  1. 第四节:跨域请求的解决方案和WebApi特有的处理方式

    一. 简介 前言: 跨域问题发生在Javascript发起Ajax调用,其根本原因是因为浏览器对于这种请求,所给予的权限是较低的,通常只允许调用本域中的资源, 除非目标服务器明确地告知它允许跨域调用. ...

  2. 第十八节:跨域请求的解决方案和WebApi特有的处理方式

    一. 简介 前言: 跨域问题发生在Javascript发起Ajax调用,其根本原因是因为浏览器对于这种请求,所给予的权限是较低的,通常只允许调用本域中的资源, 除非目标服务器明确地告知它允许跨域调用. ...

  3. JQuery的Ajax跨域请求的解决方案

    JQuery的Ajax跨域请求的解决方案 参考文章: (1)JQuery的Ajax跨域请求的解决方案 (2)https://www.cnblogs.com/amylis_chen/p/4703735. ...

  4. ASP.NET MVC和ASP.NET Web API跨域请求问题解决方案【最全】

    无论是ASP.NET MVC和ASP.NET Web API跨域请求大致分成2种方式 第一种:web.config 配置 第二种:用HttpContext.Response.AppendHeader设 ...

  5. Ajax跨域请求与解决方案

    同域与跨域请求 同域请求: 网络协议, 域名,端口号都一致,则为同域(同源)请求 http://www.sina.com.cn:8090/index.html 跨域请求: 浏览器对于javascrip ...

  6. 网站跨域请求的解决方案

    网站跨域请求的五种解决方案: 1.使用JSONP解决跨域问题(不推荐,因为支支持get请求,不支持post请求) 2.使用httpClient或者HttpUrlConnection进行内部转发请求响应 ...

  7. 前端跨域请求及解决方案

    什么是跨域请求 在前端开发编码过程中,常见的HTML标签例如:a.form.img.script.link.iframe以及ajax操作都可以指向一个资源地址或者说发起一个资源请求,那么这里所说的请求 ...

  8. JAVA web 跨域请求

    原文链接:http://www.cnblogs.com/binbang/p/5553701.html 1.http请求头 Origin: 普通的HTTP请求也会带有,在CORS(Cross-Origi ...

  9. php vue 跨域请求数据6,Vue跨域请求问题解决方案过程解析

    查看页面响应信息,提示跨域有问题 #### 二.解决上面跨域问题:根目录写入以下vue.config.js // vue.config.js module.exports = { devServer: ...

最新文章

  1. 怎么让wordpress用sqlite3 搭建轻量级博客系统
  2. 写时复制,写时拷贝,写时分裂,Copy on write
  3. 不变违规:_registerComponent(...):目标容器不是DOM元素
  4. 日常运维管理技巧十七(Nethogs进程流量监控工具)(转载)
  5. 经典PID控制算法用C语言实现!
  6. 在linux上搭建本地yum源
  7. android源码下载方式
  8. win7硬盘安装过程图解
  9. Uvaoj 11624 - Fire!
  10. redis hashmap过期_看完这篇再也不怕 Redis 面试了
  11. AX2012 学习自动生成编码
  12. 【Android 界面效果13】关于全屏和取消标题栏
  13. 向量交点坐标公式_高中数学必修1-5常用公式(定理)
  14. 初一上册数学用计算机进行运算,初一上册数学
  15. java百度贴吧一键签到,手机百度贴吧怎么一键签到?手机贴吧一键签到怎么用?
  16. 忘记压缩包密码 python 暴力破解rar密码
  17. 解决端口占用问题 Port xxxx was already in use
  18. 无人机民航执照、多旋翼、固定翼视距内驾驶员、机长考证试题
  19. 操作系统与计算机网络
  20. 2021综述:计算机视觉中的注意力机制(续四):分支注意力

热门文章

  1. 打破经典计算模拟限制,研究实现54个量子比特QAOA模拟优化
  2. 尼尔 斗技场 机器人_尼尔机械元DLC加入三个不同竞技场 DLC内容流程
  3. Element的table表格开启合计之后,合计单独使用计算公式
  4. python——5行代码采集3000+上市公司信息
  5. GoogleNet 网络结构
  6. 14个vscode常用插件 php
  7. php explode用法,PHP explode()函数用法讲解
  8. Linux虚拟机克隆后的ip设置(原虚拟机网卡设置为静态ip)
  9. (转载)豆瓣的新机遇
  10. Netty实现心跳检测