【浏览器】同源策略与跨域请求

2,043 阅读6分钟

(一)同源策略

1. 什么是同源策略

  • 同源策略是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,浏览器很容易受到XSS、CSRF等攻击。
  • 所谓同源是指 “ 协议+域名+端口 ” 三者相同,即便两个不同的域名指向同一个ip地址,也非同源。
    • 形式:协议://域名:(端口号) 同源策略.awebp

2. 同源策略的限制内容

同源策略限制以下几种行为:

  • Cookie、LocalStorage 和 IndexDB 无法读取
  • DOM和JS对象无法获得
  • AJAX 请求不能发送,被浏览器拦截了 但是有三个标签是允许跨域加载资源:
    • <img src=XXX>
    • <link href=XXX>
    • <script src=XXX>

(二)跨域

1. 跨域的原理

  • 跨域,是指浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的。
  • 跨域原理,即是通过各种方式,避开浏览器的安全限制

2. 跨域的方式与特点 ⭐⭐⭐⭐⭐

&-1. JSONP(JSON with Padding)⭐⭐⭐⭐⭐

(1)原理:

  • JSONP通过同源策略涉及不到的"漏洞",也就是像img中的srclink标签的hrefscriptsrc不受同源策略的限制
  • 利用这个特性,服务端不再返回 JSON 格式的数据,而是 返回一段调用某个函数的 js 代码,在 src 中进行了调用,这样实现了跨域。

(2)步骤:

  1. 创建一个script标签
  2. script的src属性设置接口地址
  3. 接口参数,必须要带一个自定义函数名(前后端约定好),要不然后台无法返回数据
  4. 通过自定义函数名去接受返回的数据
// 动态创建 script
var script = document.createElement('script');
script.setAttribute('type', 'text/javascript')
// 设置回调函数,用于后端返回的js代码中进行调用
function getData(data) {
    console.log(data);
}
//设置 script 的 src 属性,并设置请求地址
script.src = 'http://localhost:3000/?callback=getData';
// 让 script 生效
document.body.appendChild(script); //将返回的文本插入页面执行,让script调用回调函数

(3)JSONP 的缺点:

JSON 只支持 get,因为 script 标签只能使用 get 请求; JSONP 需要后端配合返回指定格式的数据。

&-2. CORS⭐⭐⭐⭐⭐

  • CORS:CORS是一个W3C标准,全称是”跨域资源共享”(Cross-origin resource sharing),允许浏览器向跨源服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制。
  • 服务器设置Header[Access-Control-Allow-Origin]HTTP响应头之后,浏览器将会允许跨域请求

浏览器将CORS请求分成两类:简单请求和非简单请求

&-&-1. 简单请求

  • 满足以下条件,就是简单请求:
    1. 请求方法是:HEADPOSTGET
    2. 请求头只有:AcceptAcceptLanguageContentTypeContentLanguageLast-Event-Id
  • 简单请求,浏览器自动添加一个Origin字段
  • 同时后端需要设置的请求头:
    CORS Header属性解释
    Access-Control-Allow-Origin (必须)表示接受哪些域名的请求(*为所有)
    Access-Control-Expose-Headers(可选)允许跨域请求包含(XMLHttpRequest只能拿到六个字段:Cache-ControlContent-LanguageContent-Type(仅限于 application/x-www-form-urlencoded、multipart/form-data 或 text/plain)Last-ModifiedExpiresPragma ,
    如果想拿到其他的需要使用该字段指定
    Access-Control-Allow-Credentials(可选)设置是否允许传Cookie
    (要是想传cookie,前端需要设置xhr.withCredentials = true,后端设置Access-Control-Allow-Credentials: true
    • Access-Control-Expose-Headers的六个字段属性:
      • Cache-Control:通过指定首部字段 Cache-Control 的指令,来进行缓存操作的工作机制,多个参数之间可以使用“,”分隔
      • Content-Language:会告知客户端,实体主体使用的自然语言(指中文或英文等语言);
      • Content-Type:说明实体主体内对象的媒体类型
      • Last-Modified:指明资源最终修改时间
      • Expires:会将资源失效日期告知客户端
      • Pragma:是HTTP/1.1 之前版本保留的历史遗留字段,仅作为与HTTP/1.0 的向后兼容而定义。
  • Access-Control-Max-Age 设置在86400秒不需要再发送预校验请求
  • Access-Control-Allow-Methods 设置允许跨域请求的方法
  • 简单请求示例
GET /cors? HTTP/1.1 // 请求方法是:HEAD、POST、GET
Host: localhost:2333 
Connection: keep-alive
Origin: http://localhost:2332 // 简单请求,浏览器自动添加一个`Origin`字段
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36 Accept: */* 
Referer: http://localhost:2332/CORS.html 
Accept-Encoding: gzip, deflate, br 
Accept-Language: zh-CN,zh;q=0.9 
If-None-Match: W/"1-NWoZK3kTsExUV00Ywo1G5jlUKKs"
  • 后端响应 image.png

&-&-2. 非简单请求

非简单请求则是不满足上边的两种情况之一,比如请求的方式为 PUT,或者请求头包含其他的字段

步骤:预检请求 -> 预检请求通过 -> 正式请求

  1. 非简单请求的CORS请求会在正式通信之前进行一次预检请求
    预检请求格式
    • OPTIONS:请求行 的请求方法为OPTIONS(专门用来询问的)
    • Origin:通过预检之后的请求,会自动带上Origin字段
    • Access-Control-Request-Method:请求的方式
    • Access-Control-Request-Header
    OPTIONS /cors HTTP/1.1  //`"预检"`使用的请求方法是 `OPTIONS` , 表示这个请求使用来询问的
    Origin: localhost:2333 
    Access-Control-Request-Method: PUT // 表示使用的什么HTTP请求方法 
    Access-Control-Request-Headers: X-Custom-Header // 表示浏览器发送的自定义字段 
    Host: localhost:2332 
    Accept-Language: zh-CN,zh;q=0.9 
    Connection: keep-alive 
    User-Agent: Mozilla/5.0...
    
  2. 预检请求后,服务器收到预检请求以后,检查了OriginAccess-Control-Request-MethodAccess-Control-Request-Headers字段以后,确认允许跨源请求,就可以做出回应。
    • 预检的响应头:
    HTTP/1.1 200 OK 
    Date: Mon, 01 Dec 2008 01:15:39 GMT 
    Server: Apache/2.0.61 (Unix) 
    Access-Control-Allow-Origin: http://localhost:2332 // 表示http://localhost:2332可以访问数据 
    Access-Control-Allow-Methods: GET, POST, PUT 
    Access-Control-Allow-Headers: X-Custom-Header 
    Content-Type: text/html; charset=utf-8 
    Content-Encoding: gzip Content-Length: 0 
    Keep-Alive: timeout=2, max=100 
    Connection: Keep-Alive 
    Content-Type: text/plain
    
  3. 通过预检后,才会进行正式的请求,浏览器接下来的每次请求就类似于简单请求了
    • 一旦通过了预检请求后,请求的时候就会跟简单请求一样,会有一个Origin头信息字段。
    • 通过预检之后的,浏览器发出正式请求:
    PUT /cors HTTP/1.1 
    Origin: http://api.bob.com // 通过预检之后的请求,会自动带上Origin字段 
    Host: api.alice.com X-Custom-Header: value 
    Accept-Language: en-US 
    Connection: keep-alive 
    User-Agent: Mozilla/5.0...
    

&-3. document.domain

基础域名相同 子域名不同

&-4. window.name

利用在一个浏览器窗口内,载入所有的域名都是共享一个window.name

&-5. proxy代理

目前常用方式,通过服务器设置代理

&-6. window.postMessage()

利用h5新特性window.postMessage()


参考链接