跨域以及跨域的解决方案

341 阅读3分钟

引言

在我们写前端时,与后端接口进行对接是一个很常见的操作——后端以地址的形式给前端提供一个数据接口,而前端则需要将这个接口中的数据提取出来渲染到页面上。接口对接是一个很常规的操作,但是很多时候我们在进行接口对接时会出现一个问题——跨域。接下来我们来探讨一下究竟什么是跨域,以及跨域的一些解决方案。

什么是跨域?

首先我们先来演示一下跨域究竟是什么效果

image.png

如图,当前端向后端请求数据时,页面报错,表示接口请求被跨域机制拦截,这便是跨域的影响效果。

为什么会有跨域呢?(同源策略)

其实跨域是因为浏览器具有一个策略——同源策略,必须前后端的协议号域名以及端口号都相同才符合同源策略。

协议号 域名 端口号

举个栗子:
这是一个百度的链接:https://www.baidu.com:8080/userInfo image.png

哪怕前后端的前面三项中有一项不相同,也不符合同源策略。

跨域拦截发生在什么时候呢?是前端发送请求时?还是后端响应时?

我们写一段代码来进行验证:

前端代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.1/jquery.js"></script>
    <title>Document</title>
</head>
<body>
    <button  id="btn">获取数据</button>

    <script>
        let btn = document.getElementById('btn')
        btn.addEventListener('click',()=>{
            $.ajax({
                url:'http://localhost:3000',
                data:{
                    name:'小李'
                },
                method:'get',
                success(res){
                    console.log(res);
                }
            })
        })
    </script>
</body>
</html>

后端代码

const Koa = require('koa')
const app = new Koa()

const main = (ctx,next)=>{
    console.log(ctx.query.name);
    ctx.body = 'hello world'
}

app.use(main)

app.listen(3000,()=>{
    console.log('项目已启动');
})

运行结果

在后端项目已启动的情况下,我们点击前端获取数据按钮,发现页面因为跨域拦截报了错:

image.png

而后端成功打印了数据,说明后端成功接收到了前端的请求,前端的数据请求未被拦截:

image.png

由此可见,后端响应回来的数据在浏览器接收到的时候被跨域机制拦截下来,所以跨域通常发生在后端响应时。

跨域有哪些解决方案呢?

JSONP: 利用script的src属性加载资源时不受同源策略的影响这一特性(需要前后端配合)。

可是JSONP到底是怎样利用src属性实现不受同源策略影响的呢?让我们举个例子看看。

前端代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.1/jquery.js"></script>
    <title>Document</title>
</head>
<body>
    <button  id="btn">获取数据</button>

    <script>
        const jsonp = (url,params,cb)=>{
            return new Promise((resolve,reject)=>{
                const script = document.createElement('script')

                params = {...params,cb:cb}
                const arr = Object.keys(params).map(key => `${key}=${params[key]}`)   //['name:'蜡笔小新'','age:'5'']
                script.src = `${url}?${arr.join('&')}`
                document.body.appendChild(script)
                
                // 后端会返回一个函数给前端
                // script.src === 'callback('蜡笔小新今年5岁')'
                window[cb] = (data) => {
                    resolve(data)
                }
            })
        }
        let btn = document.getElementById('btn')
        btn.addEventListener('click',()=>{
            jsonp('http://localhost:3000',{name:'蜡笔小新',age:'5'},'callback')
            .then(res => {
                console.log(res);
            })
        })
    </script>
</body>
</html>

后端代码

const Koa = require('koa')
const app = new Koa()

const main = (ctx,next)=>{
    const {name,age,cb} = ctx.query
    const userInfo = `${name}今年${age}岁`
    const str = `${cb}(${JSON.stringify(userInfo)})`    // 'callback('蜡笔小新今年5岁')'
    ctx.body = str
}

app.use(main)

app.listen(3000,()=>{
    console.log('项目已启动');
})

代码解析

首先在前端页面上放上一个button按钮,用来控制前端向后端获取数据,再定义一个 jsonp 函数,传入三个参数,第一个参数 url 是请求数据的地址;第二个参数 params 是前端想要传给后端的数据;第三个参数 cb 是一个函数名,后端响应前端时该函数被调用从而前端能够获取后端传来的数据。

当 button 按钮被点击时,jsonp函数被调用,前端利用 script.src 向后端发起请求的同时拼接了想要传给后端的数据。

后端对接收到的数据进行处理,并将函数调用以字符串的形式('callback('蜡笔小新今年5岁')')返回给前端,即前端获取到的 src 为函数调用的字符串形式。

window[cb] = (data) => {
    resolve(data)
}

// 相当于在window上定义了一个函数:
// window.callbak = (data) => {
//     resolve(data)
// }

当浏览器加载script标签的src属性时,虽然拿到的是一个字符串,但是浏览器也会将这个字符串执行,就相当于会把 callback 函数执行掉,src 请求回来的资源会被挂在window上面去执行,所以在window上定义的函数会被调用,前端就会打印出后端返回的数据了。

运行结果

image.png

image.png

后端打印了前端传过来的内容,前端也拿到了后端返回的内容,所以跨域问题被解决。当然,JSONP 方法也存在弊端,因为 src 属性只能支持 get 请求,所以JSONP也只能适用于 get 请求,同时,前端传给后端的数据会在 url 地址后面显示出来,故安全性不高