JSONP 跨域

作者: SHININGJACK | 来源:发表于2017-11-26 18:52 被阅读0次

    一、什么是同源策略?

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

    1. 不同协议,https 和 http,如 https://www.baidu.comhttp://www.baidu.com
    2. 不同端口,如 http://127.0.0.1:8080 和 http://127.0.0.1:8090
    3. 不同域名,如 a.com 和 b.com

    参考:浏览器的同源策略 | MDN

    二、什么是跨域?跨域有几种实现形式?

    跨域就是不同源的资源之间的交互。正是因为同源策略,才会出现跨域这种问题。跨域的实现方式有:

    1. JSONP
    2. CORS
    3. 降域
    4. postMessage

    三、JSONP 的原理是什么

    利用 html 的 script 标签可以引入其他 JS 资源而且不引起跨域问题,原理是 JS 是被下载到当前浏览器环境执行,所以就不算跨域,就像平常通过 cdn 引入 jQuery 一样。因此,我们可以通过这种方式,让后端返回数据,具体流程如下:

    1. 定义数据处理函数 _fun
    2. 创建 script 标签,src 的地址执行后端接口,最后加个参数 callback = _fun
    3. 服务端在收到请求后,解析参数,计算返还数据,输出 fun(data) 字符串。
    4. fun(data) 会放到 script 标签做为 js 执行。此时会调用 fun 函数,将 data 做为参数。

    四、CORS 是什么?

    CORS(Cross-Origin Resource Sharing)跨域资源共享,是一种允许 Web 应用服务器进行跨域访问控制机制,从而使跨域数据传输得以安全进行。具体通过在响应头的 Header 里面加上 Access-Control-Allow-Origin属性,允许相应的源地址访问来实现。

    五、演示三种以上跨域的解决方式

    1. JSONP

    通过 node + express 来搭建本地服务器,实现 JSONP 效果。 JS 代码:

    <!DOCTYPE html>
    <html lang="en">
    
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <meta http-equiv="X-UA-Compatible" content="ie=edge">
      <title>JSONP 演示</title>
    </head>
    
    <body>
      <button id="getData">点击获取数据</button>
      <div>
        <h1>数据展示</h1>
        <p id="dataShow"></p>
      </div>
      <script>
        const getData = document.getElementById('getData')
        getData.addEventListener('click', (e) => {
          const jsonpTag = document.getElementById('jsonp')
          if(jsonpTag){
            jsonpTag.remove()
          }
          let scriptTag = document.createElement('script')
          scriptTag.src = 'http://127.0.0.1:3000/jsonp?callback=jsonp'
          scriptTag.id = 'jsonp'
          document.querySelector('body').appendChild(scriptTag)
        })
    
        function jsonp(data) {
          const dataShow = document.getElementById('dataShow')
          const str = JSON.stringify(data)
          dataShow.innerHTML = str
        }
      </script>
    </body>
    

    服务端代码

    const express = require('express')
    const Mock = require('mockjs')
    const router = express.Router()
    
    router.use('/', (req, res, next) => {
      console.log('jsonp')
      next()
    })
    
    router.get('/', (req, res, next) => {
      const data = Mock.mock({
        'list|1-10': [{
          'id|+1': 1,
          'name|1-3': '@FIRST'
        }]
      })
      const callback = req.query.callback
      const resData = `${callback}(${JSON.stringify(data)})`
      res.end(resData)
    })
    
    module.exports = router
    

    实际演示

    可以看到这请求和相应不是同源的,因为端口不同。


    JSONP 演示

    2.CORS

    客户端代码

    <!DOCTYPE html>
    <html lang="en">
    
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <meta http-equiv="X-UA-Compatible" content="ie=edge">
      <title>CORS 演示</title>
    </head>
    
    <body>
      <button id="getData">点击获取数据</button>
      <div>
        <h1>数据展示</h1>
        <p id="dataShow"></p>
      </div>
      <script>
        const getData = document.getElementById('getData')
        const dataShow = document.getElementById('dataShow')
        getData.addEventListener('click', (e) => {
          const xhr = new XMLHttpRequest()
          xhr.open('GET', 'http://127.0.0.1:3000/cors', true)
          xhr.send()
          xhr.addEventListener('load', (data) => {
            if (xhr.status === 200) {
              const resData = data.target.response
              dataShow.innerHTML = resData
            }
          })
        })
      </script>
    </body>
    
    </html>
    

    服务端代码

    const express = require('express')
    const router = express.Router()
    const Mock = require('mockjs')
    
    router.use('/', (req, res, next) => {
      console.log('cors')
    
      res.append('access-control-allow-origin', 'http://127.0.0.1:8090')
      // res.append('withCredentials', true)
      next()
    })
    
    router.get('/', (req, res, next) => {
      const data = Mock.mock({
        'list|1-10': [{
          'id|+1': 1,
          'name|1-3': '@FIRST'
        }]
      })
      res.json(data)
    })
    
    module.exports = router
    

    实际演示

    CORS 演示

    3. 降域

    假设现在我有两个域名 a.sub.com 和 b.sub.com,但实际上指向的是同一个 ip 地址和 端口,尽管如此,因为域名不同,依旧是非同源,为了解决这个问题,通过window.domain来降域,解决跨域问题。

    修改 hosts 文件


    修改 hosts

    两个 html 文件,里边使用 iframe 演示。

    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <meta http-equiv="X-UA-Compatible" content="ie=edge">
      <title>网站 a</title>
      <style>
      iframe {
        background-color: #eee;
      }
      </style>
    </head>
    <body>
      <h1>使用降域实现跨域</h1>
      <input type="text" placeholder="http://a.sub.com:8090/a.html">
    
      <iframe src="http://b.sub.com:8090/b.html" frameborder="0"></iframe>
      <script>
      document.domain = 'sub.com'
      </script>
    </body>
    </html>
    
    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <meta http-equiv="X-UA-Compatible" content="ie=edge">
      <title>网站 b.com</title>
    </head>
    <body>
      <h1>这是网站 b</h1>
      <script>
      
      document.domain = 'sub.com'
      </script>
    </body>
    </html>
    

    在未使用window.domain降域之前,在网站 a 里是无法访问网站 b 的节点的,如下:

    为降域,获取节点失败
    而加上之后:
    降域成功演示

    全部代码地址 | GitHub

    相关文章

      网友评论

        本文标题:JSONP 跨域

        本文链接:https://www.haomeiwen.com/subject/inqlbxtx.html