同源策略和跨域 (ノ°▽°)ノ 冲鸭!征服她!!!

知识点

  • 同源策略
  • 跨域
  • JSONP
  • CORS(服务端支持)
  • document.domain
  • window.name
  • window.postMessage方法
  • iframe加form
  • 代理 Nginx配置

在这里插入图片描述

同源策略

  • ajax 请求时,浏览器要求当前网页和server必须同源(安全)
  • 同源:协议,域名,端口,三者必须一致

同源策略详情请点击存在必要性:浅谈CSRF攻击javascript

同源策略限制了从同一个源加载的文档或脚本如何与来自另外一个源的资源进行交互。这是一个用于隔离潜在恶意文件的重要安全机制。css

跨域

跨域,指的是从一个域名去请求另一个域名的资源。即跨域名请求!跨域时,浏览器不能执行其余域名网站的脚本,是由浏览器的同源策略形成的,是浏览器施加的安全限制。html

跨域的严格一点来讲就是只要协议,域名,端口有任何一个的不一样,就被看成是跨域。前端

加载图片 CSS JS 可无视同源策略

  • <img src="跨域的图片地址" />
  • <link href="跨域的css地址" />
  • <script src="跨域的js地址"></script>

提示:
咱们有时候就会引入一些cdn 的js和css等,这些地址确定不是同源的,但却可使用。
另外,有些图片若是作了防盗链限制(服务端上的处理)的话,就不能使用。
感兴趣的请点击个人另外一篇博客:图片视频等资源中转,用nodejs解决防盗链问题java

Oh my god! 那为何图片 CSS JS 可无视 同源策略???

在这里插入图片描述
浏览器这样作,都是有必定的考虑的,都是为了有一些功能,好比说:node

  • <img /> 可用于统计打点,可以使用第三方统计服务
  • <link /> <script> 可以使用CDN,CDN通常都是外域
  • <script> 可实现JSONP

全部的跨域,都必须通过server端容许和配合
未经server端容许就实现跨域,说明浏览器有漏洞,危险信号web

那么 解决跨域问题的几个办法来了!!!

在这里插入图片描述

No.1 JSONP

首先,咱们要先明白
访问 https://www.baidu.com/,服务端必定会返回一个html文件吗?
服务端能够任意动态拼接数据返回,只要符合html格式要求。
同理于: <script src="xxxxxxxx/getData.js"> 服务端也许并非返回一个getData.js静态文件,而是服务端经过拼接任何数据返回给你,只要拼接的数据格式不报错就行。ajax

那么,咱们已经知道了json

  • <script> 可绕过跨域限制
  • 服务器能够任意动态拼接数据返回
  • 因此,<script>就能够得到跨域的数据,只要服务端愿意返回

以上便是JSONP的实现原理,归纳以下:
HTML标签里,一些标签好比script,img这样的获取资源的标签是没有跨域限制的。
动态插入script标签,经过script标签引入一个js文件,这个js文件载入成功后会执行咱们在url参数中指定的函数,而且会把咱们须要的json数据做为参数传入canvas

优势: 兼容性好,简单易用,支持浏览器与服务器双向通讯。
缺点: 只支持GET请求。

简易代码示例:
后端被请求文件:jsonp.js (由后端处理拼接生成)

// jsonp.js 文件内容
abc(
    { name: 'xxx' }
)

前端请求代码:

<script>
  window.abc = function (data) {
        console.log(data)
    }
</script>
<script src="http://localhost:8002/jsonp.js?username=xxx&callback=abc"></script>

代码讲解:
前端经过<script>请求http://localhost:8002/jsonp.js并传入相应参数?username=xxx&callback=abc
后端收到请求,根据路径和参数等信息,动态处理拼接出jsonp.js文件返回给前端。
前端根据jsonp.js文件内容,执行window.abc函数,输出内部数据。

No.2 CORS

CORS是一个w3c标准,全称是"跨域资源共享"(Cross-origin resource sharing),当一个请求url的协议,域名,端口三者之间任意与当前页面地址不一样即为跨域.它容许浏览器向跨源服务器发送XMLHttpRequest请求,从而克服AJAX只能同源使用的限制.

原理: 服务器对于CORS的支持,主要就是经过设置Access-Control-Allow-Origin来进行的。若是浏览器检测到相应的设置,就能够容许Ajax进行跨域的访问。
浏览器将CORS请求分为两类:简单请求和非简单请求
只要知足如下两大条件,就属于简单请求:

  • 请求方法是如下三种方法之一:
    HEAD
    GET
    POST
  • HTTP的头信息不超出如下几种字段:
    Accept
    Accept-Language
    Content-Language
    Last-Event-ID
    Content-Type:只限于三个值application/x-www-form-urlencodedmultipart/form-datatext/plain

不一样时知足上面两个条件,就是非简单请求

代码简易示例: CORS - 服务器设置 http header

// 第二个参数填写容许跨域的域名称,不建议直接写"*"
response.setHeader("Access-Control-Allow-Origin","http://localhost:8080");
response.setHeader("Access-Control-Allow-Headers","X-Requested-With");
response.setHeader("Access-Control-Allow-Methods","PUT,POST,GET,DELETE,OPTIONS");
// 接收跨域的cookie
response.setHeader("Access-Control-Allow-Credentials","true");

No.3 经过修改document.domain来跨域

将子域和主域的document.domain设为同一个主域。
前提条件:这两个域名必须属于同一个基础域名,并且所用的协议,端口都要一致,不然没法使用document.domain来进行跨域。
详细信息请看

No.4 使用window.name来进行跨域

window对象有个name属性,该属性有个特征:即在一个窗口(window)的生命周期内,窗口载入的全部页面都是共享一个window.name的,每一个页面对window.name都有读写的权限,window.name是持久存在一个窗口载入过的全部页面中的。

No.5 使用HTML5中新引进的window.postMessage方法来跨域传送数据

window.postMessage()是HTML5的一个接口,专一实现不一样窗口不一样页面的跨域通信。

代码示例发送方:

<template>
  <div>
    <button @click="postMessage">给http://crossDomain.com:9099发消息</button>
    <iframe name="crossDomainIframe" src="http://crossdomain.com:9099"></iframe>
  </div>
</template>

<script>
export default {
  mounted () {
    window.addEventListener('message', (e) => {
      // 这里必定要对来源作校验
      if (e.origin === 'http://crossdomain.com:9099') {
        // 来自http://crossdomain.com:9099的结果回复
        console.log(e.data)
      }
    })
  },
  methods: {
    // 向http://crossdomain.com:9099发消息
    postMessage () {
      const iframe = window.frames['crossDomainIframe']
      iframe.postMessage('我是[http://localhost:9099], 麻烦你查一下你那边有没有id为app的Dom', 'http://crossdomain.com:9099')
    }
  }
}
</script>

代码示例接收方:

<template>
  <div>
    我是http://crossdomain.com:9099
  </div>
</template>

<script>
export default {
  mounted () {
    window.addEventListener('message', (e) => {
      // 这里必定要对来源作校验
      if (e.origin === 'http://localhost:9099') {
        // http://localhost:9099发来的信息
        console.log(e.data)
        // e.source能够是回信的对象,其实就是http://localhost:9099窗口对象(window)的引用
        // e.origin能够做为targetOrigin
        e.source.postMessage(`我是[http://crossdomain.com:9099],我知道了兄弟,这就是你想知道的结果:${document.getElementById('app') ? '有id为app的Dom' : '没有id为app的Dom'}`, e.origin);
      }
    })
  }
}
</script>

No.6 iframe加form

JSONP只能发送GET请求,由于本质上script加载资源就是GET。若是要发送POST请求能够以下

后端代码:

// 处理成功失败返回格式的工具
const {successBody} = require('../utli')
class CrossDomain {
  static async iframePost (ctx) {
    let postData = ctx.request.body
    console.log(postData)
    ctx.body = successBody({postData: postData}, 'success')
  }
}
module.exports = CrossDomain

前端代码:

const requestPost = ({url, data}) => {
  // 首先建立一个用来发送数据的iframe.
  const iframe = document.createElement('iframe')
  iframe.name = 'iframePost'
  iframe.style.display = 'none'
  document.body.appendChild(iframe)
  const form = document.createElement('form')
  const node = document.createElement('input')
  // 注册iframe的load事件处理程序,若是你须要在响应返回时执行一些操做的话.
  iframe.addEventListener('load', function () {
    console.log('post success')
  })

  form.action = url
  // 在指定的iframe中执行form
  form.target = iframe.name
  form.method = 'post'
  for (let name in data) {
    node.name = name
    node.value = data[name].toString()
    form.appendChild(node.cloneNode())
  }
  // 表单元素须要添加到主文档中.
  form.style.display = 'none'
  document.body.appendChild(form)
  form.submit()

  // 表单提交后,就能够删除这个表单,不影响下次的数据发送.
  document.body.removeChild(form)
}
// 使用方式
requestPost({
  url: 'http://localhost:9871/api/iframePost',
  data: {
    msg: 'helloIframePost'
  }
})

No.7 代理 Nginx配置

server{
    # 监听9099端口
    listen 9099;
    # 域名是localhost
    server_name localhost;
    #凡是localhost:9099/api这个样子的,都转发到真正的服务端地址http://localhost:9871 
    location ^~ /api {
        proxy_pass http://localhost:9871;
    }    
}
// 请求的时候直接用回前端这边的域名http://localhost:9099,这就不会跨域,而后Nginx监听到凡是localhost:9099/api这个样子的,都转发到真正的服务端地址http://localhost:9871 
fetch('http://localhost:9099/api/iframePost', {
  method: 'POST',
  headers: {
    'Accept': 'application/json',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    msg: 'helloIframePost'
  })
})

其余有趣的跨域问题

在这里插入图片描述帅气逼人的家伙们,赏个赞呗~ _(¦3」∠)_ 前端知识体系 目录结构