面试被问到的同源策略和跨域问题之解决方案

知识点

    同源策略
    跨域
    JSONP
    CORS(服务端支持)
    document.domain
    window.name
    window.postMessage方法
    iframe加form
    代理 Nginx配置
同源策略
同源:协议,域名,端口,三者必须一致

ajax 请求时,浏览器要求当前网页和server必须同源(安全)

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

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

跨域的严格一点来说就是只要协议,域名,端口有任何一个的不同,就被当作是跨域。

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

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

提示:
我们有时候就会引入一些cdn 的js和css等,这些地址肯定不是同源的,但却可以使用。
另外,有些图片如果做了防盗链限制(服务端上的处理)的话,就不能使用。

那么为什么可以无视呢?
浏览器这样做,都是有一定的考虑的,都是为了有一些功能,比如说:

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

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

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

首先,我们要先明白
访问 https://www.baidu.com/,服务端一定会返回一个html文件吗?
服务端可以任意动态拼接数据返回,只要符合html格式要求。
同理于:

那么,我们已经知道了

<script> 可绕过跨域限制
服务器可以任意动态拼接数据返回
所以,<script>就可以获得跨域的数据,只要服务端愿意返回

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

优点: 兼容性好,简单易用,支持浏览器与服务器双向通信。
缺点: 只支持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>

代码讲解:
前端通过

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

代码示例发送方:

<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'
 })
})
  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值