阅读 116

前端跨域及解决方案(CORS, JSONP)

跨域

为什么出现跨域? 出于浏览器的同源策略限制

1. 同源策略

同源策略(Sameoriginpolicy)是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,则浏览器的正常功能可能都会受到影响。

源 = 协议 + 域名 + 端口号

当一个请求url的协议、域名、端口三者之间任意一个与当前页面url不同即为跨域

非同源限制

  1. 无法读取非同源网页的 Cookie、LocalStorage 和 IndexedDB

  2. 无法接触非同源网页的 DOM

  3. 无法向非同源地址发送 AJAX 请求

有三个标签允许跨域加载资源

<img src=xxx> // 用于打点统计,统计网站可能是其他域
<link href=xxx> // link script可以使用CDN,CDN的也是其他域
<script src=xxx> // script可以用于JSONP
复制代码

注意事项:

  • 所有跨域请求都必须经过信息提供方允许
  • 如果未经允许即可获取,那是浏览器同源策略出现漏洞

2. 解决方法:CORS

阅读options,简单请求非简单请求

  1. 突破浏览器限制的一个方法
  2. CORS 是跨域资源分享(Cross-Origin Resource Sharing)的缩写
  3. 代码

在后台设置:Access-Control-Allow-Origin

 response.setHeader('Access-Control-Allow-Origin','http://XXX.com:9999')
 
 //响应头 Response Headers
header('Content-Type: text/html;charset=utf-8');
header('Access-Control-Allow-Origin:http://localhost:8080'); // *代表允许任何网址请求
header('Access-Control-Allow-Methods:POST,GET'); // 允许请求的类型
header('Access-Control-Allow-Credentials: true'); // 设置是否允许发送 cookies
header('Access-Control-Allow-Headers: Content-Type,Origin,Refer'); // 允许自定义请求头的字段 
 request.headers['refer'] //获取请求的网站
复制代码
  1. 优势:
  • 在服务端进行控制是否允许跨域,可自定义规则
  • 支持各种请求方式
  1. 问题:
  • 不兼容IE
  1. options
  • 对于非简单请求,浏览器会自动先发送一个options请求,如果发现服务器支持该请求,则会将真正的请求发送到后端,反之,如果浏览器发现服务端并不支持该请求,则会在控制台抛出错误。

3. 解决方法:JSONP

  1. IE时代的妥协
  2. 跨域资源嵌入是允许的,但是浏览器限制了Javascript不能与加载的内容进行交互,如嵌入的<script><img><link><iframe>等。
  3. 原理
  • JSONP 是 JSON with padding(填充式 JSON 或参数式 JSON)的简写。

  • JSONP实现跨域请求的原理简单的说,就是动态创建

  • JSONP 由两部分组成:回调函数和数据。回调函数是当响应到来时应该在页面中调用的函数。回调函数的名字一般是在请求中指定的。而数据就是传入回调函数中的 JSON 数据。


// 动态创建<script>标签,设置其src,回调函数在src中设置:
var script = document.createElement("script");
script.src = "https://xxx/yy?name=ccc&callback=handleResponse";
document.body.insertBefore(script, document.body.firstChild);
// 在页面中,返回的JSON作为参数传入回调函数中,我们通过回调函数来来操作数据。
function handleResponse(response){
    // 对response数据进行操作代码
}
复制代码
  1. 示例演示

背景: frank.com想访问qq.com
过程: g8EAte.png qq.com后端:

 if(path === '/friends.js'){
        const string = fs.readFileSync('./public/friends.js').toString()
        const data = fs.readFileSync('./public/friends.json').toString()
        const string2 = string.replace("{{data}}",data)
        response.write(string2)
        response.end()
      }
复制代码

friends.js

window.xxx= {{data}}
复制代码

请求成功后,friends.js会执行,数据会放到window.xxx上。

frank前端

    window.xxx= (data)=>{
        console.log(data)
    }
    // 创建script标签
    const script = document.createElement('script')
    script.src=`http://qq.com:8888/friends.js`
    // script放到body里
    document.body.appendChild(script)
    //监听script的onload事件
    script.onload = ()=>{
        console.log(window.xxx)
    }
复制代码
  1. 由此引出的问题: 这是jsonp所有网站都可以引用了 !!!
  • 解决方法:用refer检查, 可以定向分享
if (request.headers['referer'].indexOf('http://frank.com:9999') === 0){
    // 做上述JSONP的后端处理
} else {
    response.statusCode = 404
    response.end()
}
复制代码
  1. JSONP总结
  • JSONP是通过 script 标签加载数据的方式去获取数据当做 JS 代码来执行

  • 提前在页面上声明一个函数,函数名通过接口传参的方式传给后台,后台解析到函数名后在原始数据上「包裹」这个函数名,发送给前端。换句话说,JSONP 需要对应接口的后端的配合才能实现。

  1. 封装JSONP
  • 封装
function jsonp(setting){
  setting.data = setting.data || {} // 查询参数
  setting.key = setting.key||'callback' // callback名字
  setting.callback = setting.callback||function(){}  // callback方法
  setting.data[setting.key] = '__onGetData__' // callback加入到查询参数中

  window.__onGetData__ = function(data){ // 
    setting.callback (data); // 执行callback方法
  }

  var script = document.createElement('script')
  var query = []
  for(var key in setting.data){ // 将查询参数拼接
    query.push( key + '='+ encodeURIComponent(setting.data[key]) )
  }
  script.src = setting.url + '?' + query.join('&') //拼接查询参数和url
  document.head.appendChild(script)
  document.head.removeChild(script)

}

jsonp({
  url: 'http://api.jirengu.com/weather.php',
  callback: function(ret){
    console.log(ret)
  }
})
jsonp({
  url: 'http://photo.sina.cn/aj/index',
  key: 'jsoncallback',
  data: {
    page: 1,
    cate: 'recommend'
  },
  callback: function(ret){
    console.log(ret)
  }
})
复制代码
  • 封装
   function jsonp(url,data,callbackName){
    return new Promise((resolve,reject)=>{
        // 触发callback,触发后删除js标签和绑定在window上的callback
        window[callbackName] =(data)=>{
            delete window[callbackName]
            document.body.removeChild(script)
            if(data){
                resolve(data)
            } else{
                reject('没有数据返回')
            }
        }
        //  初始化url
        let dataString = url.indexOf('?') === -1 ?"?" :""
        url+=`${dataString}${callback}=${callbackName}`
        if(data){
            for(let key in data){
                url+=`&${key}=${data[key]}`
            }
        }
       //创建script标签
        const script = document.createElement('script')
        script.src = url
        // 加载后将script移除
        script.onload=()=>{
            // script.remove()
        }
        // js加载异常的情况
        script.onerror=()=>{
            document.body.remove(script)
            reject('js加载失败')
        }
         // 添加js节点到document上时,开始请求
        document.body.appendChild(script)
    })

}

jsonp('url',{
    a:1,
    b:'zzz'
},
callbackName)
.then(res=>{
    console.log(res)
})
.catch(err=>{
    console.log(err)
})
复制代码

所以JSONP是什么?

  1. 是什么
  • 创建一个script标签,去请求另一个网站的js。
  • js里会夹带一些数据,这些数据会在我的网站中调用一个全局函数(callback)
  1. 优点
  • 兼容IE
  • 可以跨域
  1. 缺点
  • 由于是script标签,所以补不到ajax那么精确的状态(比如状态码,头),他只知道成功和失败。
  • 由于是script标签,只能发get请求,不支持post。
文章分类
前端
文章标签