前端怎么实现跨域请求

实现跨域请求,我们首先要先知道什么是跨域,紧接着便是跨域的方法,最后则是跨域时cookie的处理。

什么是跨域

1.什么是同源策略及其限制内容?

同源策略 是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,浏览器很容易受到XSSCSRF等攻击。所谓同源是指"协议+域名+端口"三者相同,即便两个不同的域名指向同一个ip地址,也非同源。

同源策略限制内容有:

  • Cookie、LocalStorage、IndexedDB 等存储性内容
  • DOM 节点
  • AJAX 请求发送后,结果被浏览器拦截了

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

  • <img src=XXX>
  • <link href=XXX>
  • <script src=XXX>

2.常见跨域场景

协议子域名主域名端口号中任意一个不相同时,都算作不同域。不同域之间相互请求资源,就算作"跨域"。

特别说明两点:

  • 第一:如果是协议和端口造成的跨域问题"前台"是无能为力的。
  • 第二:在跨域问题上,仅仅是通过"URL的首部"来识别而不会根据域名对应的IP地址是否相同来判断。"URL的首部"可以理解为"协议, 域名和端口必须匹配"。

跨域并不是请求发不出去,请求能发出去,服务端能收到请求并正常返回结果,只是结果被浏览器拦截了。你可能会疑问明明通过表单的方式可以发起跨域请求,为什么 Ajax 就不会?因为归根结底,跨域是为了阻止用户读取到另一个域名下的内容,Ajax 可以获取响应,浏览器认为这不安全,所以拦截了响应。但是表单并不会获取新的内容,所以可以发起跨域请求。同时也说明了跨域并不能完全阻止 CSRF,因为请求毕竟是发出去了。

跨域有哪些方案?

1.CORS

CORS 通信过程都是浏览器自动完成,需要浏览器(都支持)和服务器都支持,所以关键在只要服务器支持,就可以跨域通信 ,CORS请求分两类,简单请求非简单请求

另外CORS请求默认不包含Cookie以及HTTP认证信息,如果需要包含Cookie,需要满足几个条件:

  • 服务器指定了 Access-Control-Allow-Credentials: true
  • 开发者须在请求中打开withCredentials 属性: xhr.withCredentials = true
  • Access-Control-Allow-Origin 不要设为星号,指定明确的与请求网页一致的域名,这样就不会把其他域名的Cookie上传

简单请求

需要同时满足两个条件,就属于简单请求:

  • 请求方法是:HEAD、GET、POST,三者之一
  • 请求头信息不超过以下几个字段:
    • Accept
    • Accept-Language
    • Content-Language
    • Last-Event-Id
    • Content-Type:值为三者之一application/x-www/form/urlencodedmultipart/form-datatext/plain

需要这些条件是为了兼容表单,因为历史上表单一直可以跨域

浏览器直接发出CORS请求,具体来说就是在头信息中增加Origin字段,表示请求来源来自哪个域(协议+域名+端口),服务器根据这个值决定是否同意请求。如果同意,返回的响应会多出以下响应头信息

javascript 复制代码
const xhr = new XMLHttpRequest();
xhr.withCredentials = true;
xhr.onreadystatechange = function() {
  if (xhr.readyState === 4 && xhr.status === 200) {
    console.log('收到数据:', xhr.responseText);
  }
};
xhr.open('GET', 'http://example.com/data.json', true);
xhr.send();
javascript 复制代码
Access-Control-Allow-Origin: http://yourdomain.com
Access-Control-Allow-Credentials: true

在这个示例中,我们使用了 XMLHttpRequest 对象来进行跨域请求。我们设置了 withCredentials 属性为 true,表示允许跨域请求。同时,在服务器响应中需要设置以上响应头,其中 Access-Control-Allow-Origin 表示允许哪个域名下的请求,Access-Control-Allow-Credentials 表示是否允许发送凭证(如 cookie、HTTP 认证等)。

非简单请求

比如 PUT 或 DELETE 请求,或 Content-Type 为 application/json ,就是非简单请求。

非简单 CORS 请求,正式请求前会发一次 OPTIONS 类型的查询请求,称为预检请求,询问服务器是否支持网页所在域名的请求,以及可以使用哪些头信息字段。只有收到肯定的答复,才会发起正式XMLHttpRequest请求,否则报错

预检请求的方法是OPTIONS,它的头信息中有几个字段

  • Origin: 表示请求来自哪个域,这个字段是必须的
  • Access-Control-Request-Method:列出CORS请求会用到哪些HTTP方法,这个字段是必须的
  • Access-Control-Request-Headers: 指定CORS请求会额外发送的头信息字段,用逗号隔开

OPTIONS请求次数过多也会损耗性能,所以要尽量减少OPTIONS请求,可以让服务器在请求返回头部添加

javascript 复制代码
Access-Control-Max-Age: Number // 数字 单位是秒

2.JSONP

JSONP 是一种常见的跨域请求方法。它是通过在 HTML 页面中添加一个 script 标签,来请求一个位于其他域名下的 JavaScript 文件。该 JavaScript 文件返回的数据需要使用一个回调函数进行包装,以便在原始页面中可以接收到数据。

javascript 复制代码
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>JSONP 示例</title>
  </head>
  <body>
    <script>
      function jsonpCallback(data) {
        console.log('收到数据:', data);
      }
      const url = 'http://example.com/data.json?callback=jsonpCallback';
      const script = document.createElement('script');
      script.src = url;
      document.body.appendChild(script);
    </script>
  </body>
</html>

在这个示例中,我们通过添加一个 script 标签来请求 http://example.com/data.json 这个地址下的数据。由于该地址存在跨域问题,我们指定了一个名为 jsonpCallback 的回调函数,并将回调函数名作为参数传递到了地址中。当服务器返回数据时,会自动调用回调函数并将数据作为参数传入。

3.代理

如果无法使用 JSONP 或 CORS,可以尝试使用代理服务器来转发请求。在前端中,可以向自己的服务器发送请求,然后让服务器代理请求其它域名下的资源,并将结果返回给前端。

javascript 复制代码
const xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
  if (xhr.readyState === 4 && xhr.status === 200) {
    console.log('收到数据:', xhr.responseText);
  }
};
xhr.open('GET', '/proxy?url=http://example.com/data.json', true);
xhr.send();

在这个示例中,我们向自己的服务器发送了一个 GET 请求,并在请求地址中添加了一个名为 proxy 的参数。服务器接收到请求后会将该请求转发到 http://example.com/data.json 地址下,并将响应结果返回给前端。

4.WebSocket

WebSocket 是一种支持跨域请求的协议,它允许客户端和服务器之间建立一个双向的通信通道。通过 WebSocket 可以实现跨域传输数据。

因为WebSocket请求头信息中有Origin 字段,表示请求源来自哪个域,服务器可以根据这个字段判断是否允许本次通信,如果在白名单内,就可以通信

客户端代码:

javascript 复制代码
const socket = new WebSocket('ws://example.com/ws');
socket.onopen = function() {
  console.log('连接已建立');
  socket.send('你好,服务器!');
};
socket.onmessage = function(event) {
  console.log('收到消息:', event.data);
};
socket.onerror = function(event) {
  console.log('发生错误:', event);
};

在这个示例中,我们通过创建一个 WebSocket 对象来连接 ws://example.com/ws 这个地址下的 WebSocket 服务。当连接建立成功后,我们发送了一段消息给服务器。当服务器返回消息时,会自动触发 onmessage 事件,并将服务器发送的数据作为参数传入。

需要注意的是,在使用 WebSocket 进行跨域通信时,服务器必须显式地指定允许哪些源访问 WebSocket 服务。服务器需要在握手阶段返回一个 Upgrade 头部,并在该头部中指定 Origin 头部的值。以下是一个使用 Node.js 实现的 WebSocket 服务的示例代码:

javascript 复制代码
const WebSocketServer = require('ws').Server;
const wss = new WebSocketServer({ port: 8080 });
wss.on('connection', function(ws) {
  console.log('连接已建立');
  ws.on('message', function(message) {
    console.log('收到消息:', message);
    ws.send('你好,客户端!');
  });
});

在这个示例中,我们使用了 ws 模块创建了一个 WebSocket 服务,并监听在 8080 端口上。当客户端连接成功后,会触发 connection 事件,并返回一个 WebSocket 对象 ws。当客户端发送消息时,会自动触发 message 事件,并将客户端发送的数据作为参数传入。在这个示例中,我们将收到的消息直接返回给客户端。

5.postMessage

postMessage 是一种在不同窗口或跨域框架之间进行通信的机制。它允许一个窗口向另一个窗口发送消息,无论这两个窗口是否同源。

使用 postMessage 进行通信时,需要提供目标窗口的引用,并通过调用 postMessage 方法发送消息。以下是一个示例:

javascript 复制代码
// 发送消息
const targetWindow = window.opener; // 获取目标窗口的引用
const message = 'Hello, world!';
const targetOrigin = 'http://example.com'; // 目标窗口的源
targetWindow.postMessage(message, targetOrigin);

// 接收消息
window.addEventListener('message', function(event) {
  if (event.origin === 'http://example.com') {
    const message = event.data;
    console.log('收到消息:', message);
    // 处理接收到的消息
  }
});

在这个示例中,首先获取了目标窗口的引用 targetWindow ,然后通过调用 postMessage 方法向目标窗口发送消息。发送的消息可以是任意类型的数据,例如字符串、对象等。

在接收消息的窗口中,通过添加 message 事件监听器来监听消息的接收,然后通过 event.data 获取收到的消息内容。可以通过 event.origin 来验证消息来源的域,确保只接受来自指定域的消息。

需要注意的是,为了确保安全性,应该始终在 postMessage 中指定目标窗口的源,以防止恶意窗口的干扰。同时,在接收消息时,应该对消息来源进行验证,并仅处理来自可信任源的消息。

总结来说,postMessage 提供了一种安全的跨窗口通信机制,可以在不同窗口、跨域框架之间进行双向通信。

这里只介绍几种开发中用的比较多的,几乎用不到的比如:

  • document.domain + iframe:适用主域名相同,子域名不同的跨域场景
  • window.name + iframe:利用name值最长可以 2M ,并用不同页面或不同域名加载后依然存在的特性
  • location.hash + iframe:适用通过 C 页面来实现 A 页面与 B 页面通信的场景

在跨域请求中,Cookie 的处理需要额外注意。默认情况下,跨域请求是不会携带 Cookie 的,这是出于安全考虑。

如果你想在跨域请求中携带 Cookie,有以下几种方法:

  1. CORS(跨域资源共享):在服务端设置响应头部,允许指定的源携带 Cookie 。具体做法是,在服务器返回的响应头中添加 Access-Control-Allow-Credentials: true ,表示允许携带凭证(如 Cookie);同时,需要指定允许访问的源,可以使用 Access-Control-Allow-Origin: http://yourdomain.com 来限制只允许特定域名的请求携带 Cookie。

  2. 代理:将跨域请求发送到自己的服务器上,然后再由自己的服务器转发请求,并将响应返回给前端。由于请求是在同源下发起的,因此可以携带 Cookie。前端将请求发送给自己的服务器,然后自己的服务器再将请求发送给目标服务器,并将目标服务器的响应返回给前端。

  3. 在 URL 中传递参数:可以将需要的信息以参数的形式附加在 URL 上,而不是通过 Cookie 传递。这样不会触发同源策略,也不会引起跨域问题。

需要注意的是,使用 Cookie 进行跨域请求时,还需要设置 XMLHttpRequest 对象的 withCredentials 属性为 true。例如:

javascript 复制代码
const xhr = new XMLHttpRequest();
xhr.withCredentials = true;
xhr.open('GET', 'http://example.com/api', true);
xhr.send();

同时,在服务端也要对跨域请求进行相应的处理,以确保安全性。

相关推荐
运维-大白同学20 分钟前
将django+vue项目发布部署到服务器
服务器·vue.js·django
烦躁的大鼻嘎36 分钟前
【Linux】深入理解GCC/G++编译流程及库文件管理
linux·运维·服务器
乐大师37 分钟前
Deepin登录后提示“解锁登陆密钥环里的密码不匹配”
运维·服务器
ac.char43 分钟前
在 Ubuntu 上安装 Yarn 环境
linux·运维·服务器·ubuntu
敲上瘾44 分钟前
操作系统的理解
linux·运维·服务器·c++·大模型·操作系统·aigc
Myli_ing1 小时前
HTML的自动定义倒计时,这个配色存一下
前端·javascript·html
dr李四维1 小时前
iOS构建版本以及Hbuilder打iOS的ipa包全流程
前端·笔记·ios·产品运营·产品经理·xcode
雯0609~1 小时前
网页F12:缓存的使用(设值、取值、删除)
前端·缓存
℘团子এ2 小时前
vue3中如何上传文件到腾讯云的桶(cosbrowser)
前端·javascript·腾讯云
学习前端的小z2 小时前
【前端】深入理解 JavaScript 逻辑运算符的优先级与短路求值机制
开发语言·前端·javascript