问题来源
为啥会有这样一个问题?
前端开发中,避免不了要引用其他域中的资源(js、css、picture、document…);
通常,通过ajax来实现对其他域资源的引用(ajax,音:/ajaks/,Asynchronous JavaScript and XML。其核心实现是XMLHttpRequest;最大的优点在于在不重新加载页面的前提下,实现与服务器交换数据并更新部分网页内容。)
但ajax这存在安全隐患:可以请求任意不同域的资源,可通过js操作不同域下的DOM,如果用户访问了恶意网站,那么用户的cookie将会泄露,继而用户隐私泄露、财产损失...用户登录了某银行的官网之后无意浏览了恶意网页,黑客便能够获取银行网站的页面并加载器js脚本,继而盗刷用户银行卡...
故,W3C制定“同源策略”The Same Origin Policy,简写SOP。它是浏览器最核心、最基本的安全策略。可以说Web是构建在同源策略基础上的,浏览器只是针对同源策略的一种实现。
对于JavaScript来说,以下情况被认为与http://store.company.com/dir2/one.html是同源或不同源:
SOP Principles
URL中的scheme,host,port,三者相同则浏览器认为资源来自同一源;
通俗地讲,来自不同源的文档(document)相互隔离。比如一个来自http://example.com/doc.html的document尝试访问https://example.com/target.html的DOM,浏览器将会拒绝,为啥?因为第一个文档的源(http,example.com,80)与第二个文档的源(https,example.com,443)不同。
SOP同源策略可以在避免被xss、scrf攻击方面起到它应起到的作用,但也阻止了浏览器的跨域(必要的跨域需求下还是需要跨域的),那么如何解决SOP下的跨域问题呢? 这就得请CORS出场了。
CORS
是一个W3C标准,全称是“跨域(源)资源共享”(Cross-Origin Resource Sharing)。
浏览器将CORS请求分成两类:简单请求和非简单请求。
凡是不满足这两个条件的CORS请求,就属于非简单请求。浏览器对这两种请求的处理是不一样的。
先来看一个测试用例:
简单请求
处理一个简单请求,以一个客户端的简单请求为例。下面代码展示了javascript的一个GET请求,以及浏览器发送的实际请求。CORS中特殊的头粗体显示:
首先需要注意的是,一个合法的CORS请求总是包含Origin头。这个Origin头是浏览器加上去的,并且不能被用户控制。这个头的值是来自请求源的协议(例如http),域(例如bob.com),和端口(只在不是默认端口的情况下包含,例如81);例如http://api.alice.com.
Origin头的存在不意味着请求是一个跨域请求。虽然所有跨域的请求都会包含Origin头,一些同域请求同样也会包含Origin头。例如,火狐在同域请求中不包含Origin头。但是Chrome和Safari在同域的POST/PUT/DELETE请求(同域的GET请求不会有Origin头)中会包含Origin头。下面有一个同域请求包含Origin头的例子:
值得庆幸的是浏览器在同域请求时不会期望CORS请求头。同域请求的响应发送给用户,不管是否有CORS请求头。然而,如果服务器代码返回了如果Origin不匹配允许的域列表的错误,确保包含请求的来源域。
所有CORS相关的的头都是Access-Control为前缀的。下面是每个头的一些细节。
Access-Control-Allow-Origin(必须)-这个头在所有合法的CORS响应中必须被包含;发送头会引起CORS请求失败。这个头的值可以是Origin请求头的回应(就像上面的例子里面一样),或者是允许来自任何域请求的"*"。如果你希望任何站点都可以获取数据,使用"*"就很好。但是如果你希望更好的控制谁可以获取你的数据,在头中使用一个实际的值。
Access-Control-Allow-Credentials(可选)-默认情况下,cookies在CORS请求中不被包含。使用这个头意味着在CORS请求中应当包含cookies.这个头的唯一合法值是true(全部小写)。如果不需要cookies,不要包含这个头(而不是把值设置为false).
Access-Control-Allow-Credentials头与XMLHttpRequest2的withCredentials属性结合使用。所有这些属性都必须设置为true以便于CORS请求能够成功。如果withCredentials是true,但是没有Access-Control-Allow-Credentials头,请求会失败(反之亦然)。
上面提到过,除非你确定需要cookies包含在请求中,否则不设置这个头。
Access-Control-Expose-Headers(可选)-XMLHttpRequest2对象有一个返回特别响应头值的getResponseHeader()方法。在CORS请求中,getResponseHeader()只能获取简单的响应头。简单响应头定义如下:
如果希望客户端获取其他头,必须用Access-Control-Expose-Headers头。这个头的值是一个逗号分隔的想要公开给客户端的响应头列表。
非简单请求
上面就是如何处理简单GET请求,但如果想要做更多的事情要怎么做?也许你想要支持其他HTTP动作,像PUT或DELETE,或者想要使用Content-Type:application/json支持JSON.你就需要处理所谓非简单请求。
非简单请求看上去像到客户端的一个单一请求,但是实际上在遮罩下包含了两个请求。浏览器首先发出一个预先请求,就好像询问服务器应允进行实际请求。一旦被允许,浏览器发送实际请求。浏览器透明的处理这两个请求的细节。预响应也可以被缓存下来以便不需要在每个请求前都发送。
下面是一个非简单请求的例子:
像简单请求一样,浏览器给每个请求加上Origin头,包含预请求。预请求像HTTP OPTIONS请求一样发出(所以确保服务器可以响应这个方法).他也包含额外的一些头:
Access-Control-Request-Method-HTTP实际请求的方法。这个请求头总是被包含,即使HTTP请求是一个简单请求,就像早些定义的那些(GET,POST,HEAD).
Access-Control-Request-Headers-一个在请求中包含的非简单头的逗号分隔的列表。
预请求是在发送实际请求前,问询服务器是否允许实际请求的方式。服务器应当检查上面的两个头来确认HTTP方法和请求头是合法的及可以接受的。
如果HTTP请求方法和头是合法的,服务器会如下响应:
Access-Control-Allow-Origin(必须)-如简单响应一样,预响应必须包含这个头。
Access-Control-Allow-Method(必须)-所支持的HTTP方法逗号分隔的列表。注意虽然预请求只是询问HTTP方法是否允许,响应头可以包含所有支持的HTTP方法。这个是有帮助的,因为预响应可能会被缓存,所以单一的预响应可以包含多种请求类型的细节。
Access-Control-Allow-Headers(如果请求头包含Access-Control-Allow-Headers就必须包含)-逗号分隔的所支持的请求头列表。像上面的Access-Control-Allow-Methods一样,这个可以列出服务器支持的所有头(不仅仅是在预请求中请求的头)。
Access-Control-Allow-Credentials(可选)-同简单请求。
Access-Control-Max-Age(可选)-在每个请求上进行预请求是代价很高的,因为浏览器对于客户端的请求会进行两次请求。这个头的值允许预响应缓存一个指定的秒数。
一旦预请求给到了应允,浏览器发送实际请求。实际请求看起来像简单请求,响应以同样的方式处理:
如果服务器想否决CORS请求,可以只返回一个通用的响应(像HTTP 200),不带任何CORS请求头。如果预请求时的HTTP方法或者头不合法,服务器可能想要否决请求。由于在响应中没有CORS特定的头,浏览器断定请求是不合法的,不发送实际请求:
如果在CORS请求中有错误,浏览器会触发客户端的onerror事件处理。也会在控制台上打印下面的错误:
浏览器不会给为什么错误会发生的细节信息,只告诉你出了些问题。
本文引用自:https://www.cnblogs.com/linda586586/p/4351452.html