【前端入门笔记16】同源策略,和跨域的两个方法:CORS和JSONP

369 阅读4分钟

文章回答以下问题:

  • 什么是同源策略
  • 为什么需要同源策略
  • 跨域是什么
  • 跨域的方法1:CORS
  • 跨域的方法2:JSONP

同源策略

什么是同源策略

最初,同源策略(same-origin policy)的含义是指,A 网页设置的 Cookie,B 网页不能打开,除非这两个网页“同源”。这是浏览器的功能。所谓“同源”指的是“三个相同”:

  • 协议相同
  • 域名相同
  • 端口相同

举例👇

  • http://www.example.com/dir2/other.html:同源
  • http://example.com/dir/other.html:不同源(域名不同)
  • http://v2.www.example.com/dir/other.html:不同源(域名不同)
  • http://www.example.com:81/dir/other.html:不同源(端口不同)
  • https://www.example.com/dir/page.html:不同源(协议不同)

同源策略的目的

同源政策的目的,是为了保证用户信息的安全,防止恶意的网站窃取数据。

不同网站发送的请求,几乎没有区别(referer有区别),但是只区分referer不够安全,所以浏览器为了用户隐私,设置了严格的同源策略。

限制范围

如果非同源,共有三种行为受到限制。

  • 无法读取非同源网页的 Cookie、LocalStorage 和 IndexedDB。
  • 无法接触非同源网页的 DOM。
  • 无法向非同源地址发送 AJAX 请求(可以发送,但浏览器会拒绝接受响应)。

同源策略限制的是数据访问,我们引用CSS、JS和图片的时候,其实并不知道他的内容,我们只是在引用。

那么,当我们面对不同网站要互相访问数据的需求时,我们该如何跨域?答:CORS 和 JSONP

什么是跨域

跨域就是为了摆脱同源策略,跨网站访问数据

跨域方法1:CORS

CORS全称是“跨域资源共享”(Cross-origin resource sharing)。它允许浏览器向跨域的服务器,发出XMLHttpRequest请求,从而克服了 AJAX 只能同源使用的限制。

如何使用

允许某个网站访问

response.setHeader('Access-Control-Allow-Origin','http://baodu.com')  //允许http://baodu.com访问
const request = new XMLHttpRequest()
request.open('get', 'http://qq.com:8888/friends.json')  //直接访问其他网站的数据
request.onreadystatechange = () => {
    if (request.readyState === 4 && request.status === 200) {
        console.log(request.response)
    }
}
request.send()
//成功打出log,因为qq.com:8888添加了Access-Control-Allow-Origin

但是ie不支持CORS,有些网站也不支持CORS,那该怎么办?用JSONP

跨域方法2:JSONP

举例:如果网站B希望访问网站A的数据,用JSONP如何实现?

第一步(网站A):网站A将数据放到一个js文件中

window['{{xxx}}']({{ data }})

第二步(网站A):将数据替换到js文件中

···
} else if (path === '/friends.js') {
        if (request.headers["referer"].indexOf("http://hack.com:9990") === 0) {  //此处进行referer检查,但依然不够安全,所以后面会用到随机数
            response.statusCode = 200
            response.setHeader('Content-Type', 'text/javascript;charset=utf-8')
            const string = fs.readFileSync('./public/friends.js').toString()
            const data = fs.readFileSync('./public/friends.json').toString()
            const string2 = string.replace('{{ data }}', data).replace('{{xxx}}', query.functionName)
            response.write(string2)
            response.end()
        } else {
            response.statusCode = 404
            response.end()
        }
···
//当有人请求friends.js时,将拿到的包含随机数的query.functionName,以及转变为date字符串的json数据发送回去

第三步(网站B):引用网站A中的js文件

const random = 'hackJSONPCallBackName' + Math.random() //为了不占用任何一个全局变量,加入随机数

//当拿到网站A的数据后,通过window拿到data
//这实际上是一个回调
window[random] = (data) => {
    console.log(data)
}

const script = document.createElement('script')
script.src = `http://qq.com:8888/friends.js?functionName=${random}`  //
script.onload = () => {  //引用后直接删除,以免body中内容越来越多
    script.remove()
}
document.body.appendChild(script)  //添加到body中

到此,网站B就通过window拿到了网站A的数据

封装

function jsonp(url) {
    //因为是异步(需要目标网站调用),所以此处用promise
    return new Promise((resolve, reject) => {
        const random = 'hackJSONPCallBackName' + Math.random()
        window[random] = (data) => {
            resolve(data)
        }
        const script = document.createElement('script')
        script.src = `${url}?functionName=${random}`  //我用的时候传入一个要获取数据的url
        script.onload = () => {
            script.remove()
        }
        script.onerror = () => {
            reject()
        }
        document.body.appendChild(script)
    })
}

jsonp('http://qq.com:8888/friends.js')
    .then((data) => {  //成功后给我一个函数,得到data
        console.log(data)
    })

什么是JSONP

JSONP是在跨域时,因为当前浏览器,或者其他原因,无法使用CORS,我们需要用到的一种跨域方法。

于是我们在自己网站创建一个script标签,去请求另一个网站的js文件,js文件中会包含一些数据,另一个网站的js文件在自己网站调用一个全局函数(此处是回调,注意是其它网站调用,我只是定义了函数内容),函数中包含我们想要的数据。回调的名字可以是一个随机数,我们将这个随机数用callback的形式传给后台,后台会将这个函数再次返回给我们并执行。

JSONP优点

  • 兼容ie
  • 可以跨域

JSON缺点

  • 因为是script标签,所以无法得知状态码,也不知道响应头,只知道成功或者失败
  • 因为是script标签,所以只能发get请求,不支持post