Blog icon indicating copy to clipboard operation
Blog copied to clipboard

同源策略与跨域请求

Open coconilu opened this issue 6 years ago • 1 comments

概述

跨域请求的存在是因为浏览器的同源策略。

default

同源策略

同源策略是浏览器的一个安全功能,不同源的客户端脚本在没有明确授权的情况下,不能读写对方资源。若地址里面的协议、域名和端口号均相同则属于同源。

同源策略要求协议、主机名、端口号要一致。当然我们可以通过document.domain来修改同源要求,不过只能修改为它的父域。

比如:http://store.company.com/dir/other.html的同源要求为http://store.company.com:80,可以修改为document.domain = "company.com";

至于浏览器为什么会有同源策略,无非就是安全问题。为了保证用户信息的安全,防止恶意的网站窃取数据。

现代浏览器在安全性和可用性之间选择了一个平衡点。在遵循同源策略的基础上,选择性地为同源策略“开放了后门”。在浏览器中,

  1. 标签嵌入跨域脚本。语法错误信息只能被同源脚本中捕捉到。
  2. 标签嵌入CSS。
  3. 通过 展示的图片。
  4. 通过
  5. 通过 @font-face 引入的字体。一些浏览器允许跨域字体( cross-origin fonts),一些需要同源字体(same-origin fonts)。
  6. 通过

同源策略会影响下面情况:

  1. 脚本不可以获取和操作另一个源的DOM、Cookie、LocalStorage 和 IndexDB,比如不同源的iframe
  2. 不能通过Web API:FetchXMLHttpRequest请求另一个源的数据

跨域请求

但是我们的很多业务场景都需要访问获取另一个源的数据。 这里还是有很多技巧的。

1. 借助img标签和script标签的src

仅支持http的get方法

2. JSONP

原理是借助script标签的src,需要服务器配合

3. CORS,需要客户端和服务器同时支持。

跨域资源共享,Cross-Origin Resource Sharing (CORS)。

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

简单请求

若请求满足所有下述条件,则该请求可视为“简单请求”:

使用下列方法之一:
    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 对象。

简单请求的请求头:

客户端:Origin

服务端:Access-Control-Allow-Origin

post 和 put的区别

post是非幂等的,使用POST来创建资源,使用POST更新全部或一部分值。

put是幂等的,使用PUT更新某一资源,则必须要更新资源的全部属性。

预检请求

当请求满足下述任一条件时,即应首先发送预检请求:

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

预检阶段交互的请求头:

客户端:Origin、Access-Control-Request-Method、Access-Control-Request-Headers

服务器:Access-Control-Allow-Origin、Access-Control-Allow-Methods、Access-Control-Allow-Headers、Access-Control-Max-Age

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

附带身份凭证的请求

一般而言,对于跨域 XMLHttpRequest 或 Fetch 请求,浏览器不会发送身份凭证信息。如果要发送凭证信息,需要设置 XMLHttpRequest 的某个特殊标志位。将 XMLHttpRequest 的 withCredentials 标志设置为 true,从而向服务器发送 Cookies。

但是,如果服务器端的响应中未携带 Access-Control-Allow-Credentials: true ,浏览器将不会把响应内容返回给请求的发送者。

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

相关的请求头部

下面这些首部字段无须手动设置。 当开发者使用 XMLHttpRequest 对象发起跨域请求时,它们已经被设置就绪。

Origin,表明预检请求或实际请求的源站。
Access-Control-Request-Method,将实际请求所使用的 HTTP 方法告诉服务器。
Access-Control-Request-Headers,将实际请求所携带的首部字段告诉服务器。

相关的响应头部

Access-Control-Allow-Origin:<origin> | *
Access-Control-Expose-Headers,让服务器把允许浏览器访问的头放入白名单。
Access-Control-Max-Age,指定了preflight请求的结果能够被缓存多久
Access-Control-Allow-Credentials,指定了当浏览器的credentials设置为true时是否允许浏览器读取response的内容。
Access-Control-Allow-Methods,用于预检请求的响应。
Access-Control-Allow-Headers,用于预检请求的响应。

4. iframe系

  • iframe+window.name。window对象有个name属性,该属性有个牛逼的地方就是:在同一个窗口中,我不管你页面怎么变,我window.name的值是一直存在的,在同一个窗口任意读写,并且支持非常长的name值(2MB)。
  • iframe+location.hash。location.hash改变了,但是不会引起页面刷新。
  • iframe+window.postMessage。HTML5新增了个window.postMessage方法,允许跨窗口通信,且不必同源。

5. document.domain

域。

6. 服务器代理

这也是最后的办法。

参考:

浏览器的同源策略 八种方式实现跨域请求 跨域的那些事儿 iframe跨域 HTTP访问控制(CORS) 对于浏览器的同源策略你是怎样理解的呢? 浏览器同源政策及其规避方法 POST 还是 PUT

coconilu avatar Aug 18 '18 07:08 coconilu

console.log(document.getElementById('test'));

document.getElementById('test').innerHTML = '<h1>hello</h1>'

localStorage.setItem('test', 'testStr');

console.log(document.cookie);

document.cookie = '';

console.log(document.cookie);

fetch('http://0.0.0.0:4321/test.js')
  .then(res => {
    console.log(res);
  })

image

模拟了非同源,但是只有ajax 不能发送,没有出现: Cookie、LocalStorage 和 IndexDB 非同源不可读取 脚本不可以获取和操作另一个源的DOM,比如不同源的iframe

是否是以讹传讹

shenxiang11 avatar Jul 24 '19 01:07 shenxiang11