跨域是什么?如何解决跨域问题?

269 阅读5分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第7天,点击查看活动详情

前言

本篇文章总结了一些跨域的概念,解决跨域的一些方法。同掘友们一起分享本人的学习成果,如果有写的不对的地方,欢迎大家提出issues,相互学习,共同进步!

一. 跨域

当前页面中的某个接口请求的地址和当前页面的地址,如果协议、域名、端口其中有一项不同,就说该接口跨域了。

提示:跨域限制访问,其实是浏览器的限制

image.png 一般发起请求跨域会报如下错误:

image.png

1. 跨域的原因

浏览器为了保证网页的安全,出的同源协议策略。什么是同源策略呢? 百度词条给出如下解释:

同源策略(Same origin policy)是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,则浏览器的正常功能可能都会受到影响。可以说Web是构建在同源策略基础之上的,浏览器只是针对同源策略的一种实现。

同源策略是浏览器的行为,是为了保护本地数据不被JavaScript代码获取回来的数据污染,因此拦截的是客户端发出的请求回来的数据接收,即请求发送了,服务器响应了,但是无法被浏览器接收。

举例: 当一个浏览器的两个tab页中分别打开来 百度和谷歌的页面

当浏览器的百度tab页执行一个脚本的时候会检查这个脚本是属于哪个页面的,

即检查是否同源,只有和百度同源的脚本才会被执行。

如果非同源,那么在请求数据时,浏览器会在控制台中报一个异常,提示拒绝访问。

同源是指: 协议,域名,端口都相同,就是同源, 否则就是跨域

2.解决方式

1. CORS(通过设置后端允许跨域实现)

在请求头添加 Access-Control-Allow-Origin 属性,浏览器会判断响应中 Access-Control-Allow-Origin 值是否和当前的地址相同,匹配成功后才会做响应处理,否则继续报错。 缺点:会忽略cookie,而且对浏览器版本有一定的要求

res.setHeader('Access-Control-Allow-Origin', '*');

res.setHeader("Access-Control-Allow-Methods", "GET, PUT, OPTIONS, POST");

2. node中间件、nginx反向代理

跨域限制的时候浏览器不能跨域访问服务器,node中间件和nginx反向代理,都是让请求发给代理服务器,静态页面面和代理服务器是同源的,然后代理服务器再向后端服务器发请求,服务器和服务器之间不存在同源限制。

2.1 node中间件

中间件就是一个函数。 在服务器开启之后和在路由响应之前,执行的一个函数,可以在这个中间件函数里边做我们想做的事。 并且这个函数里可以操作request,response

var express = require('express')
var app = express()

// 中间件
app.use((req, res, next) => {
    console.log('LOGGED1')
    next()
})

app.use((req, res, next) => {
    console.log('LOGGED2')
    next() //执行下一个中间件
})

app.get('/', (req, res) => {
    res.send('hello world')
})

app.listen(4399, () => {
    console.log('服务器开启了');
    
})

2.2 Nginx反向代理

把web项目部署到和后端接口同源的当前本地的服务器上。Nginx配置就不在这边写了,一般由运维来做。前端主要做的是在vue.config.js中进行代理配置。

devServer: {
    // 其他代码省略。。。。。
    
    // 代理配置
    proxy: {
      // /api 是看接口文档,每当访问本地的/api接口时,会转化为访问真实的服务器
      '/api': {
        target: 'http://localhost:3000' // 我们要代理的真实接口地址
      }
    }

3. JSONP

通过动态创建script标签,通过script标签的src请求没有跨域限制来获取资源。缺点:只能发起get请求

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="/jsonp解决跨域.js"></script>
    <script>
        //事先准备好的函数fn和后端约定好
        //backData就是后端通过参数传递过来的
        function fn(backData) {
            console.log('这是一个fnsb函数')
            console.log(backData)
        }
    </script>
</head>
<body>
    //把事先准备好的函数名,传递给要访问的后端接口
    <script src="http:127.0.0.1:8080/all?callback=fn"></script>
</body>
</html>

4. postmessage

H5新增API,通过发送和接收API实现跨域通信。

otherWindow.postMessage(message, targetOrigin, [transfer]); otherWindow

  • otherWindow: 其他窗口的一个引用,比如 iframe 的 contentWindow 属性、执行 window.open 返回的窗口对象、或者是命名过或数值索引的 window.frames。

对将接收消息的窗口的引用。获得此类引用的方法包括:
window.open :生成一个新窗口然后引用它;

window.opener : 引用产生这个的窗口;

HTMLIFrameElement.contentWindow<iframe>从其父窗口引用嵌入式,可简单理解为从父级页面向自己页面进行数据传递;

window.parent:从嵌入式内部引用父窗口<iframe>,可简单理解为从子级页面向父级页面传递数据(当前场景下所用的window.parent);

window.frames + 索引值(命名或数字);

  • message:向目标窗口发送的数据。它将会被结构化克隆算法序列化,所以无需自己序列化(部分低版本浏览器只支持字符串,所以发送的数据最好用JSON.stringify() 序列化)。

  • targetOrigin:通过 targetOrigin 属性来指定哪些窗口能接收到消息事件,可以是字面字符串 "*"(表示没有偏好),也可以是URI。但请始终提供一个具体的targetOrigin,而不是*。如果不提供具体的目标,则会泄露你发送的信息。

  • transfer(可选):一系列可转移的对象,与消息一起被转移。这些对象的所有权被赋予目的地一方,它们不再能在发送方使用。

//发送方:
window.parent.postMessage('isClosed', 'http://yourhost.com');


//接收方:
window.addEventListener("message", msgHandler, false);
function msgHandler(event){
  if (event.origin !== "http://yourhost.com")    return;

  // ...
}

实例参考文章: postMessage-跨域通信postMessage - 跨域消息传递

5. webSockets

它是一种浏览器的API,它的目标是在一个单独的持久连接上提供全双工、双向通信。(同源策略对web sockets不适用)

原理:在JS创建了web socket之后,会有一个HTTP请求发送到浏览器以发起连接。取得服务器响应后,建立的连接会使用HTTP升级从HTTP协议交换为web sockt协议。 只有在支持web socket协议的服务器上才能正常工作。