跨域 && 如何解决跨域

1,326 阅读4分钟

相信我们在工作或者是学习中,前后端的交互都会涉及到跨域,那什么是跨域,什么时候会发生跨域,跨域有哪些解决方式,今天我们就来聊一聊跨域以及跨域的一些解决方式。

什么是跨域?

跨域:指的是浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的,是浏览器对javascript施加的安全措施。

什么是同源策略?

协议号 - 域名 - 端口号 ,只有当这三个条件同时满足相同时,我们就称之为符合同源策略,同源策略也可以看做是一个协议。

https://  www.baidu.com   :8080    /test
协议号         域名        端口号    路径

通常我们导航的url都是由这四部分组成的。

什么情况下会发生跨域?

当我们前端向后端(服务器)发送接口请求,后端相应返回给前端的数据,在浏览器接收到时被跨域机制拦截下来了,浏览器为了安全问题一般都限制了跨域访问。

如何解决跨域?

首先我们要了解有三个h5里的标签允许加载跨域资源

1. <img src="XXX" >
2. <link href="XXX" > // 通常用于引入css文件
3. <script src="XXX" > // 通常用于引入js文件

我们接下来要讲的第一个解决跨域的方法其实就是利用了script标签的src属性。

JSONP

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

代码实现:

html:
<button id="btn">接口请求</button>
js:
<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="harvey"','age=20', 'cb:xxx']
                script.src = `${url}?${arr.join('&')}`   // http://localhost:3000?name="harvey
"&age=20&cb=xxx
                document.body.appendChild(script)
                // 后端会返回一个函数给前端

                // script.src === 'callback()'  === callback('harvey今年18岁')
                window[cb] = (data) => {
                    resolve(data)
                }

            })
        }

        let btn = document.getElementById('btn')
        btn.addEventListener('click',()=>{
           jsonp('http://localhost:3000',{name: 'harvey',age: 18},'callback')
            .then(res => {
                console.log(res);
            })
        })
    </script>
使用node来模拟的一个本地后端项目(其中安装了koa依赖)
app.js:
const Koa = require('koa')
const app = new Koa()

const main = (ctx,next) => {
    // console.log(ctx.query);
    const {name,age,cb} = ctx.query
    const userInfo = `${name}今年${age}岁`
    const str = `${cb}(${JSON.stringify(userInfo)})`  // 'callback()'
    //我们可以使用 JSON.stringify() 方法将 JavaScript 对象转换为字符串。
    ctx.body = str
}
app.use(main)


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

我们一起来分析JSONP是如何实现的,首先我们在JS中定义了一个函数,它接收三个参数,分别是接口的请求地址、可能携带的参数、以及一个回调函数,它返回的是一个Promise对象,方便我们后续使用.then进行后面的操作,我们先在js中创建了一个script标签,我们再对第二个参数进行结构,再直接给script标签的src属性上赋值,然后将这个script标签直接添加到html中,当我们点击事件之后向后端的 ' http://localhost:3000 ' 这个地址发送接口请求,携带的参数是 {name: 'harvey',age: 18},回调函数我们就放'callback'字符串,而我们后端将前端传递过来的数据进行一些操作时候,使得返回给前端的内容变成字符串,里面是回调函数的调用以及参数,即'cb(harvey今年18岁)'。前端接收到之后js中会将'cb(harvey今年18岁)'这一串字符串里面的内容执行,所以 window[cb] = (data) => { resolve(data) } 这个就会执行,相当于我们将cb(harvey今年18岁)这个执行结果作为参数data传给了window[cb],再将结果通过Promise对象的resolve出去了,那么调用JSONP的.then就会将执行结果输出在控制台。从而解决跨域问题。

其核心就在于将后端返回的结果加载在script的src属性上,从而跨域加载资源。

079aa65535f24eb9acfe9c53818dd67.jpg

缺点:JSONP-- 只适用于get请求,因为script的src加载资源默认就是get请求,所以不支持post等其他类型的请求 get请求,会将请求的数据加载url后面。

Cors(Cross-Origin Resouce Sharing)

其实有一个koa/cors的依赖,只需要我们在后端npm i @koa/cors -save安装好这个依赖,并且在全局引入并使用即可,这样后端就开启了cros,允许跨域操作,那在学习阶段,我们直接来实现用原生node如何封装cros呢?

前端代码:
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.1/jquery.js"></script>
// 引入一个ajax,方便发接口请求
<button id="btn">获取数据</button>

js:
<script>
        
        let btn = document.getElementById('btn')
        btn.addEventListener('click',()=>{
            $.ajax({
                url: 'http://localhost:3000',
                data: {
                    name: 'harvey'
                },
                headers: { 
                    // 为了告诉后端,你返回的响应头的数据类型应该是xxx
                    'Content-Type': 'application/json; charset=utf-8'
                },
                method: 'get',
                success(res) {
                    console.log(res);
                }
            })
        })
    </script>
node // app.js
const http = require('http') // 引入http模块

const server = http.createServer((req,res) => {
    // 开启cros, 后端用于写响应头的设置
    res.writeHead(200, {
        // 允许跨域
        "Access-Control-Allow-Origin": "*",  // 配置同源白名单
        "Access-Control-Allow-Methods": "GET, POST, OPTIONS,PUT",
        // 不管向浏览器返回什么类型都可以
        // "Access-Control-Allow-Headers": "Content-Type"
    }) 

    res.end('hello cros')
})

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

前端部分就是一个简单的使用ajax来发起一个接口请求,请求类型为GET类型,后端我们直接使用node的http模块,来生成一个服务,关键在于配置白名单,以及你接口请求的类型,如果你接口请求的响应头中有数据类型要求,那也需要配置 "Access-Control-Allow-Headers": "Content-Type" ,res.end('hello cros')是向前端返回的内容,只要配置好以上三项,无论什么前端都可以向我电脑本地的3000端口发送接口请求。

cros只需要后端开启,就能实现跨域,但是只能用于开发阶段。 缺点:任何前端都可以向后端发接口请求。

node代理

原理:利用后端与后端进行数据交互时没有同源策略,所以我们创建一个自己的后端,我们的后端开启cros,然后将数据传输给我们的前端,这样我们就可以获取到其他后端的数据。

<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.1/jquery.js"></script>
// 引入一个ajax,方便发接口请求
<button id="btn">获取数据</button>

js:
<script>
        
        let btn = document.getElementById('btn')
        btn.addEventListener('click',()=>{
            $.ajax({
                url: 'http://localhost:3001',
                data: {
                    name: 'harvey'
                },
                headers: { 
                    // 为了告诉后端,你返回的响应头的数据类型应该是xxx
                    'Content-Type': 'application/json; charset=utf-8'
                },
                method: 'get',
                success(res) {
                    console.log(res);
                }
            })
        })
    </script>

其实与上一个方法的前端代码几乎一摸一样,只不过将申请的地址换成了我们自己写好的后端的地址。

后端:
// 别人的后端(我们要获取数据的后端,端口号为3000)
const Koa = require('koa')
const app = new Koa()

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


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

// 自己的后端

const http = require('http') // 引入http模块

const server = http.createServer((req,res) => {
    // 开启cros, 后端用于写响应头的设置
    res.writeHead(200, {
        // 允许跨域
        "Access-Control-Allow-Origin": "*",  // 配置同源白名单
        "Access-Control-Allow-Methods": "GET, POST, OPTIONS,PUT",
        // 不管向浏览器返回什么类型都可以
        "Access-Control-Allow-Headers": "Content-Type"
    }) 


    // 向别人的后端请求数据
    http.request({
        host: '127.0.0.1', // 域名
        port: '3000', // 接口
        path: '/', // 路径
        method: 'GET'
    }, proxyRes => {
        // console.log(proxyRes);
        proxyRes.on('data', result => {
            res.end(result.toString())
        })
        
    }).end()
})

server.listen(3001, () => {
    console.log('my项目已启动');
})

我们写的后端接收到我们需要的数据之后,再返回给我们的前端即可。

缺点:与cros方式一致任何前端都可以向我们的后端发接口请求,仅适用于开发阶段。

以上就是我对跨域以及跨域的一些解决办法的个人见解,当然解决跨域的方法当然不仅仅只有这三种,有任何疑问,都可在评论区沟通交流,感谢你的观看!

点赞.jpg