前端怎么实现跨域请求

实现跨域请求,我们首先要先知道什么是跨域,紧接着便是跨域的方法,最后则是跨域时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();

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

相关推荐
腾讯TNTWeb前端团队7 小时前
helux v5 发布了,像pinia一样优雅地管理你的react状态吧
前端·javascript·react.js
范文杰10 小时前
AI 时代如何更高效开发前端组件?21st.dev 给了一种答案
前端·ai编程
拉不动的猪10 小时前
刷刷题50(常见的js数据通信与渲染问题)
前端·javascript·面试
拉不动的猪10 小时前
JS多线程Webworks中的几种实战场景演示
前端·javascript·面试
FreeCultureBoy11 小时前
macOS 命令行 原生挂载 webdav 方法
前端
uhakadotcom11 小时前
Astro 框架:快速构建内容驱动型网站的利器
前端·javascript·面试
uhakadotcom12 小时前
了解Nest.js和Next.js:如何选择合适的框架
前端·javascript·面试
uhakadotcom12 小时前
React与Next.js:基础知识及应用场景
前端·面试·github
uhakadotcom12 小时前
Remix 框架:性能与易用性的完美结合
前端·javascript·面试
uhakadotcom12 小时前
Node.js 包管理器:npm vs pnpm
前端·javascript·面试