面试官:什么是跨域,哪种是跨域,解决方法?

797 阅读6分钟

问题:跨域,哪种是跨域,解决方法?

解答:
跨域(Cross-Origin)是指在浏览器中,一个网页试图从不同的域名、协议或端口请求资源时,由于浏览器的同源策略(Same-Origin Policy)限制而无法直接访问这些资源。这种限制是为了保护用户的安全,防止恶意网站通过脚本读取其他网站的数据。

1. 什么是同源策略?

同源策略(Same-Origin Policy)是浏览器的一种安全机制,它规定了只有当两个 URL 的协议、域名和端口号完全相同时,才能被认为是“同源”。如果其中任何一个不同,则认为它们是“跨域”。

同源的条件:

  • 协议相同:例如 http:// 和 https:// 是不同的协议。
  • 域名相同:例如 example.com 和 sub.example.com 是不同的域名。
  • 端口号相同:例如 example.com:8080 和 example.com:3000 是不同的端口。

示例:

URL 1URL 2是否同源
http://example.comhttp://example.com
http://example.comhttps://example.com
http://example.comhttp://sub.example.com
http://example.comhttp://example.com:8080

2. 哪些场景会导致跨域?

以下几种情况会导致跨域问题:

  1. 不同域名

    • 客户端向不同的域名发送请求。例如,前端页面托管在 http://example.com,但需要向 http://api.example.org 发送请求。
  2. 不同协议

    • 前端页面使用 http:// 协议,但请求的目标服务器使用 https:// 协议,或者反之亦然。
  3. 不同端口

    • 前端页面运行在 http://example.com:8080,但请求的目标服务器运行在 http://example.com:3000
  4. 子域名不同

    • 前端页面托管在 http://sub1.example.com,但请求的目标服务器托管在 http://sub2.example.com
  5. 文件系统中的跨域

    • 如果你直接从本地文件系统(如 file://) 访问远程服务器资源,也会导致跨域问题。

3. 常见的跨域请求类型

  1. 简单请求(Simple Request)

    • 符合以下条件的请求被认为是“简单请求”:

      • 请求方法为 GETPOST 或 HEAD
      • 请求头仅包含标准的 HTTP 头(如 AcceptAccept-LanguageContent-LanguageContent-Type),且 Content-Type 的值为 application/x-www-form-urlencodedmultipart/form-data 或 text/plain
  2. 预检请求(Preflight Request)

    • 当请求不符合简单请求的标准时,浏览器会自动发送一个 OPTIONS 请求(称为“预检请求”),以确认服务器是否允许该跨域请求。预检请求通常用于检查复杂的请求(如带有自定义请求头或使用 PUTDELETE 等非简单方法的请求)。
  3. 凭据请求(Credentials Requests)

    • 如果请求中包含凭据(如 Cookie、HTTP 认证信息等),则必须特别处理跨域问题。默认情况下,浏览器不会将凭据发送到跨域服务器,除非服务器明确允许。

4. 跨域解决方案

针对不同的跨域场景,有多种解决方案可以使用。以下是常见的几种方法:

4.1. CORS(跨域资源共享,Cross-Origin Resource Sharing)

CORS 是一种由服务器设置的 HTTP 响应头机制,允许服务器明确告知浏览器哪些来源可以访问其资源。CORS 是目前最常用、最推荐的跨域解决方案。

服务器端配置:
  • Access-Control-Allow-Origin:指定允许访问资源的源(Origin)。可以设置为特定的域名(如 http://example.com),也可以设置为通配符 *(允许所有源访问,但不建议在生产环境中使用)。
  • Access-Control-Allow-Methods:指定允许的 HTTP 方法(如 GETPOSTPUT 等)。
  • Access-Control-Allow-Headers:指定允许的自定义请求头。
  • Access-Control-Allow-Credentials:允许跨域请求携带凭据(如 Cookie)。设置为 true 时,浏览器会将凭据发送到服务器,并且 Access-Control-Allow-Origin 不能设置为 *,必须指定具体的源。
示例:
HTTP/1.1 200 OK
Content-Type: application/json
Access-Control-Allow-Origin: http://example.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Allow-Credentials: true

4.2. JSONP(JSON with Padding)

JSONP 是一种早期的跨域解决方案,主要用于支持 GET 请求。它的原理是利用 <script> 标签不受同源策略限制的特点,通过动态创建 <script> 标签来加载外部资源,并将返回的数据作为 JavaScript 函数调用的形式执行。

工作原理:
  • 客户端动态创建一个 <script> 标签,并将请求的 URL 作为 src 属性。
  • 服务器返回的数据被包装在一个回调函数中,浏览器会执行该回调函数并将数据传递给它。
示例:
<script>
  function handleResponse(data) {
    console.log('Received data:', data);
  }
</script>

<script src="http://api.example.org/data?callback=handleResponse"></script>
注意:
  • JSONP 只支持 GET 请求,安全性较低,容易受到 XSS 攻击,因此不推荐在现代应用中使用。

4.3. 代理服务器

代理服务器 是指在客户端和目标服务器之间放置一个中间服务器,客户端向代理服务器发送请求,代理服务器再向目标服务器转发请求并返回结果。这样可以绕过浏览器的同源策略,因为客户端和代理服务器是同源的。

实现方式:
  • 在后端服务器上搭建一个代理服务,接收前端的请求并转发到目标服务器。
  • 代理服务器可以缓存响应数据,减少对目标服务器的负载。

通过配置代理服务器,可以将跨域请求转发到同源服务器上,然后由代理服务器代为发送请求。

  • 应用场景: 可以在开发环境中通过配置 Webpack 的 DevServer 代理,或在生产环境中通过配置 Nginx 反向代理。

  • 示例:

// Webpack DevServer 配置
devServer: {
    proxy: {
        '/api': {
            target: 'http://api.example.com',
            changeOrigin: true,
            pathRewrite: { '^/api': '' }
        }
    }
}

前端代码可以直接请求 /api/ 路径:

fetch('/api/data')
  .then(response => response.json())
  .then(data => console.log(data));

4.4. WebSocket

WebSocket 是一种全双工通信协议,允许客户端和服务器之间建立持久连接。与 HTTP 不同,WebSocket 连接不受同源策略的限制,因此可以实现跨域通信。

示例:
const socket = new WebSocket('ws://api.example.org/socket');

socket.onopen = () => {
  console.log('WebSocket connection established');
};

socket.onmessage = (event) => {
  console.log('Received message:', event.data);
};

socket.onerror = (error) => {
  console.error('WebSocket error:', error);
};

4.5. PostMessage API

PostMessage API 允许不同窗口或 iframe 之间的跨域通信。它可以用于在同一个浏览器窗口内的多个页面之间传递消息,即使这些页面位于不同的域。

示例:

假设你有两个页面:

  • 页面 A:http://example.com/pageA.html
  • 页面 B:http://example.org/pageB.html

页面 A 可以通过 postMessage 向页面 B 发送消息:

// 页面 A
const iframe = document.getElementById('iframeB');
iframe.contentWindow.postMessage('Hello from Page A', 'http://example.org');

页面 B 可以监听消息:

// 页面 B
window.addEventListener('message', (event) => {
  if (event.origin !== 'http://example.com') return; // 验证消息来源
  console.log('Received message:', event.data);
});

5. 总结

解决方案适用场景优点缺点
CORS最常用的跨域解决方案灵活、支持复杂请求、安全性高需要服务器端配置
JSONP早期的跨域解决方案,仅支持 GET简单、易于实现安全性低、只支持 GET
代理服务器绕过浏览器同源策略支持所有类型的请求、安全性高需要额外的服务器配置
WebSocket实现实时双向通信不受同源策略限制需要服务器支持 WebSocket 协议
PostMessage API不同窗口或 iframe 之间的通信简单、灵活仅限于同一浏览器窗口内的通信

6. 进一步探讨

  • 你是否有遇到过实际项目中的跨域问题?你是如何解决的?
  • 你是否想了解更多关于 CORS 的具体配置和最佳实践?例如如何处理带凭据的请求?
  • 你是否对 WebSocket 协议感兴趣?它是如何实现实时双向通信的,适用于哪些场景?