一天一题:说说你的跨域的了解

513 阅读5分钟

首先 了解 同源策略

在制定Html规则时,为了安全的考虑,一个源的脚本(网页,网站)不能与另一个源的资源进行交互,

所以就引发一个词叫做“同源策略”。

同源策略:同源策略是一种约定,它是浏览器最核心的也最基本的安全功能,如果缺少了同源策略,则

浏览器的正常功能可能会受到影响。

同源:所谓同源(即指在同一个域),就是两个页面具有相同的协议(protocol),主机(host)端口号(port)

什么是 跨域

当协议,主机,和端口号有一个不同时,就是跨域。

例如:

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

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

跨域会导致的问题

  • Cookie,LocalStorage无法读取
  • DOM和js对象无法读取,主要是iframe(可以说有iframe的对象无法读取)
  • Ajax请求不能发送

解决跨域的方法

对最主要的AJAX跨域来说(也就是平常调接口时):

  • (后端)服务器配置CORS(跨域资源共享)

  • (后端)node.js或nginx,反向代理,把跨域改造成同域

  • (前端)将JSON升级成JSONP,在JSON的基础上,利用<script>标签可以跨域的特性,加上头设置

对iframe跨域来说:

  • H5提供了postMessage()的方法,可以在父子页面进行通信(加分项)

No.1 JSONP

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

那么,我们已经知道了

<script> 可绕过跨域限制 服务器可以任意动态拼接数据返回 所以,<script>就可以获得跨域的数据,只要服务端愿意返回 以上即是JSONP的实现原理,概括如下:

在HTML标签里,一些标签比如script,img这样的获取资源的标签是没有跨域限制的。 动态插入script标签,通过script标签引入一个js文件,这个js文件载入成功后会执行我们在url参数中指定的函数,并且会把我们需要的json数据作为参数传入

优点: 兼容性好,简单易用,支持浏览器与服务器双向通信。

缺点: 只支持GET请求。

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-urlencoded、multipart/form-data、text/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的一个接口,专注实现不同窗口不同页面的跨域通讯。

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'
  })
})