前端跨域问题
什么是跨域
跨域是指一个域下的文档或脚本试图去请求另一个域下的资源,这里跨域是广义的。
- 资源的跳转:如
<a>
标签超链接,重定向,表单提交等; - 资源的嵌入:
<link>、<script>、<img>、<iframe>
标签等; - 脚本请求:通过 JS 发起的 Ajax 请求,DOM 和 JS 对象的跨域操作灯等;
通常所说的跨域是狭义的,是由浏览器同源策略限制的一类场景;
同源策略(Same origin policy)
同源策略是浏览器最基本的安全策略,如果缺少了同源策略,浏览器很容易受到 XSS、CSFR 攻击;同源是指:如果协议、主机、端口对于两个页面是相同的,则两个页面是同源的;
下表是给出了对http://store.company.com:80的同源检测:
| URL | 结果 | 原因 |
| — | — | — |
|http://store.company.com/dir/inner/another.html | 成功 | 同一域名 |
|http://store.company.com/dir2/other.html | 成功 | 同一域名下不同文件夹 |
|https://store.company.com/secure.html | 失败 | 不同的协议 ( https ) |
|http://store.company.com:81/dir/etc.html| 失败 | 不同的端口 ( 81 ) |
|http://news.company.com/dir/other.html | 失败 | 主域相同,子域不同( news ) |
|http://news.company.com/dir/other.html | 失败 | 域名不同 |
解决方案
主域相同的跨域
document.domain
+ iframe
实现跨域
完全不同源的跨域(两个页面之间的通信)
- 通过
location.hash
跨域 - 通过
window.name
跨域 - HTML5 通过引入全新的跨文档通信 API
window.postMessage
允许跨文档通信
AJAX 请求不同源的跨域
JSONP
通过 JSONP
请求跨域,网页通过添加一个脚本元素,向服务器请求 JSON 数据,这种做法不受同源政策限制;服务器收到请求后,将数据放在一个指定名字的回调函数里传回来:
1 | /* client */ |
服务器返回:
1 | /* server */ |
WebSocket
通过WebSocket
跨域,是一种通信协议,不执行同源政策;
跨域资源共享(CORS):
当一个资源请求一个其它域名的资源时会发起一个跨域HTTP请求(cross-origin HTTP request)。比如说,域名A的某 Web 应用通过<img>
标签引入了域名B的某图片资源,域名A的 Web 应用就会导致浏览器发起一个跨域 HTTP 请求。在当今的 Web 开发中,使用跨域 HTTP 请求加载各类资源(包括CSS、图片、JavaScript 脚本以及其它类资源),已经成为了一种普遍且流行的方式。
众所周知,出于安全考虑,浏览器会拦截跨域请求的返回结果。比如,使用XMLHttpRequest对象
和Fetch
发起 HTTP 请求就必须遵守『同源策略』。 具体而言,Web 应用程序通过XMLHttpRequest对象或Fetch能且只能向同域名的资源发起 HTTP 请求,而不能向任何其它域名发起请求。
简单请求
所谓的简单,是指:
- 只使用
GET, HEAD
或者POST
请求方法。如果使用POST 向服务器端传送数据,则数据类型(Content-Type)只能是application/x-www-form-urlencoded, multipart/form-data 或 text/plain
中的一种; - 不会使用自定义请求头(类似于 X-Modified );
浏览器在发送简单跨域请求时需加上origin
请求头,标明站点来源;同时服务器需设置Access-Control-Allow-Origin
响应头为『*』或指明的站点地址;
预请求
不同于上面讨论的简单请求,『预请求』要求必须先发送一个 OPTIONS
请求给目的站点,来查明这个跨站请求对于目的站点是不是安全可接受的。这样做,是因为跨站请求可能会对目的站点的数据造成破坏(POST 请求不具有幂等性)。 当请求具备以下条件,就会被当成预请求处理:
- 请求以 GET, HEAD 或者 POST 以外的方法发起请求。者,使用 POST,但请求数据为 application/x-www-form-urlencoded, multipart/form-data 或者 text/plain 以外的数据类型。比如说,用 POST 发送数据类型为
application/xml 或者 text/xml
的 XML 数据; - 使用自请求头(比如添加诸如 X-PINGOTHER)
1 | var invocation = new XMLHttpRequest(); |
如上,以 XMLHttpRequest 创建了一个 POST 请求,为该请求添加了一个自定义请求头(X-PINGOTHER: pingpong),并指定数据类型为 application/xml。所以,该请求是一个『预请求』形式的跨站请求。浏览器使用一个 OPTIONS 发送了一个『预请求』。Firefox 3.1 根据请求参数,决定需要发送一个『预请求』,来探明服务器端是否接受后续真正的请求。 OPTIONS 是 HTTP/1.1 里的方法,用来获取更多服务器端的信息,是一个不应该对服务器数据造成影响的方法。 随同 OPTIONS 请求,以下两个请求头一起被发送:
1 | Access-Control-Request-Method: POST |
假设服务器成功响应返回部分信息如下:
1 | //表明服务器允许http://foo.example的请求 |
附带凭证信息的请求
XMLHttpRequest 和访问控制功能中最有趣的特性就是,发送凭证请求(HTTP Cookies和验证信息)的功能。一般而言,对于跨站请求,浏览器是不会发送凭证信息的。但如果将 XMLHttpRequest 的一个特殊标志位withCredentials
设置为true,浏览器就将在请求中附带凭证信息。
1 | //http://foo.example站点的脚本向http://bar.other站点发送一个GET请求,并设置了一个Cookies值 |
假设服务器成功响应返回部分信息如下:
1 | Access-Control-Allow-Origin: http://foo.example |
如果服务器端的响应中,如果没有返回Access-Control-Allow-Credentials: true
的响应头,那么浏览器将不会把响应结果传递给发出请求的脚本程序,以保证信息的安全。同时Access-Control-Allow-Origin
必须设置为请求传递的Origin
,否则响应会失败;
CORS的优点:CORS支持所有类型的HTTP请求,是跨域HTTP请求的根本解决方案;