MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Vue网络请求 如何优雅地处理跨域问题

2023-12-154.4k 阅读

一、跨域问题的本质

在前端开发中,跨域问题是一个常见且必须解决的难题。当一个前端应用尝试从一个源(origin,由协议、域名和端口组成)向另一个不同的源发起 HTTP 请求时,就会触发跨域问题。这一限制是由浏览器的同源策略(Same - Origin Policy)导致的。

同源策略是一种安全机制,旨在防止恶意脚本在未经授权的情况下,访问其他站点的数据。例如,一个恶意网站可能试图通过 JavaScript 发起请求,获取用户在银行网站上的敏感信息。如果没有同源策略,这种攻击将变得非常容易。同源要求协议、域名和端口完全相同,只要其中任何一项不同,就会被视为跨域。

比如,以下几种情况属于跨域:

  1. 不同协议http://example.comhttps://example.com
  2. 不同域名http://example.comhttp://another.com
  3. 不同端口http://example.com:8080http://example.com:8081

二、Vue 中的网络请求

在 Vue 项目中,我们通常使用 axios 进行网络请求。axios 是一个基于 Promise 的 HTTP 客户端,它可以在浏览器和 Node.js 中使用。以下是一个简单的使用 axios 发送 GET 请求的示例:

import axios from 'axios';

export default {
  methods: {
    async fetchData() {
      try {
        const response = await axios.get('http://api.example.com/data');
        console.log(response.data);
      } catch (error) {
        console.error('Error fetching data:', error);
      }
    }
  }
};

当上述代码中的请求 URL 与当前 Vue 应用所在的源不同时,就会触发跨域问题。浏览器会阻止该请求,并在控制台中显示类似如下的错误信息:

Access to XMLHttpRequest at 'http://api.example.com/data' from origin 'http://localhost:8080' has been blocked by CORS policy: No 'Access - Control - Allow - Origin' header is present on the requested resource.

三、处理跨域问题的常见方法

(一)JSONP

  1. 原理 JSONP(JSON with Padding)利用 <script> 标签没有跨域限制的特性来实现跨域请求。它的基本思路是在 HTML 页面中动态创建一个 <script> 标签,通过这个标签的 src 属性来请求一个跨域的 JavaScript 文件,这个文件的内容是一个函数调用,函数的参数就是我们需要的数据。

  2. 代码示例 首先,假设服务器端(以 Node.js 为例)返回 JSONP 数据的代码如下:

    const http = require('http');
    const url = require('url');
    
    const server = http.createServer((req, res) => {
      const query = url.parse(req.url, true).query;
      const callback = query.callback;
      const data = { message: 'Hello from server' };
      res.writeHead(200, { 'Content - Type': 'application/javascript' });
      res.end(`${callback}(${JSON.stringify(data)})`);
    });
    
    server.listen(3000, () => {
      console.log('Server running on port 3000');
    });
    

    在 Vue 端,使用原生 JavaScript 实现 JSONP 请求:

    <template>
      <div>
        <button @click="fetchData">Fetch Data</button>
      </div>
    </template>
    
    <script>
    export default {
      methods: {
        fetchData() {
          const script = document.createElement('script');
          const callbackName = 'jsonpCallback';
          window[callbackName] = (data) => {
            console.log(data);
            // 处理完数据后移除script标签
            document.body.removeChild(script);
          };
          script.src = `http://localhost:3000?callback=${callbackName}`;
          document.body.appendChild(script);
        }
      }
    };
    </script>
    

    JSONP 的优点是兼容性好,在一些较老的浏览器中也能使用。缺点也很明显,它只支持 GET 请求,并且安全性较低,因为它是通过在页面中插入脚本的方式来获取数据,容易受到 XSS 攻击。

(二)CORS(Cross - Origin Resource Sharing)

  1. 原理 CORS 是一种现代浏览器支持的跨域解决方案,它通过在服务器端设置响应头来告诉浏览器允许哪些源访问该资源。主要涉及到以下几个关键的响应头:
    • Access - Control - Allow - Origin:指定允许跨域请求的源,可以是具体的源(如 http://localhost:8080),也可以是 *(表示允许所有源)。
    • Access - Control - Allow - Methods:指定允许的 HTTP 方法,如 GETPOSTPUTDELETE 等。
    • Access - Control - Allow - Headers:指定允许的请求头字段。
  2. 简单请求与预检请求
    • 简单请求:满足以下条件的请求被视为简单请求:
      • 请求方法是 GETPOSTHEAD 之一。
      • 除了浏览器自动设置的请求头(如 ConnectionUser - Agent 等),没有自定义的请求头。
      • Content - Type 的值是 application/x - www - form - urlencodedmultipart/form - datatext/plain 之一。 对于简单请求,浏览器会直接发送请求,并根据服务器返回的 Access - Control - Allow - Origin 等响应头来判断是否允许跨域。
    • 预检请求:不满足简单请求条件的请求会先发送一个预检请求(OPTIONS 请求),询问服务器是否允许该实际请求。服务器需要在预检请求的响应中设置正确的 Access - Control - Allow - OriginAccess - Control - Allow - MethodsAccess - Control - Allow - Headers 等响应头。如果预检请求通过,浏览器才会发送实际的请求。
  3. 代码示例 在 Node.js 中使用 Express 框架设置 CORS 响应头:
    const express = require('express');
    const app = express();
    
    app.use((req, res, next) => {
      res.setHeader('Access - Control - Allow - Origin', 'http://localhost:8080');
      res.setHeader('Access - Control - Allow - Methods', 'GET, POST, PUT, DELETE');
      res.setHeader('Access - Control - Allow - Headers', 'Content - Type, Authorization');
      next();
    });
    
    app.get('/data', (req, res) => {
      const data = { message: 'Data from server' };
      res.json(data);
    });
    
    const port = 3000;
    app.listen(port, () => {
      console.log(`Server running on port ${port}`);
    });
    
    在 Vue 端使用 axios 发送请求:
    <template>
      <div>
        <button @click="fetchData">Fetch Data</button>
      </div>
    </template>
    
    <script>
    import axios from 'axios';
    
    export default {
      methods: {
        async fetchData() {
          try {
            const response = await axios.get('http://localhost:3000/data');
            console.log(response.data);
          } catch (error) {
            console.error('Error fetching data:', error);
          }
        }
      }
    };
    </script>
    
    CORS 是目前最常用的跨域解决方案,它支持各种 HTTP 方法,安全性较高,因为它是基于服务器端的配置,而不是像 JSONP 那样依赖于客户端的脚本插入。

(三)代理服务器

  1. 原理 代理服务器是一种中间服务器,它位于前端应用和目标服务器之间。前端应用向代理服务器发送请求,代理服务器再将请求转发到目标服务器,并将目标服务器的响应返回给前端应用。由于前端应用与代理服务器处于同一源(通常是在开发环境中,代理服务器与前端开发服务器运行在相同的地址和端口),所以不会触发跨域问题。

  2. Vue 开发环境中的代理配置(基于 webpack - dev - server) 在 Vue 项目的 vue.config.js 文件中,可以配置代理服务器。假设目标服务器地址为 http://api.example.com,配置如下:

    module.exports = {
      devServer: {
        proxy: {
          '/api': {
            target: 'http://api.example.com',
            changeOrigin: true,
            pathRewrite: {
              '^/api': ''
            }
          }
        }
      }
    };
    

    上述配置表示,当 Vue 应用中发送以 /api 开头的请求时,会将请求代理到 http://api.example.comchangeOrigin 设置为 true 表示修改请求头中的 Origin 字段,使其与目标服务器的源一致。pathRewrite 用于重写请求路径,将 /api 前缀去掉,这样目标服务器接收到的请求路径就是正常的路径。

    在 Vue 组件中发送请求时,使用代理后的路径:

    <template>
      <div>
        <button @click="fetchData">Fetch Data</button>
      </div>
    </template>
    
    <script>
    import axios from 'axios';
    
    export default {
      methods: {
        async fetchData() {
          try {
            const response = await axios.get('/api/data');
            console.log(response.data);
          } catch (error) {
            console.error('Error fetching data:', error);
          }
        }
      }
    };
    </script>
    

    代理服务器的优点是在开发环境中配置简单,能够方便地解决跨域问题。但它只适用于开发环境,在生产环境中,通常需要在服务器端进行更全面的跨域配置,如使用 CORS。

四、生产环境中的跨域处理策略

在生产环境中,由于涉及到实际的用户访问和安全问题,处理跨域需要更加谨慎。

(一)CORS 的优化配置

  1. 白名单设置 在生产环境中,不建议使用 Access - Control - Allow - Origin: *,因为这意味着允许所有源访问资源,存在安全风险。应该设置具体的白名单,只允许信任的源进行跨域访问。例如,如果生产环境的前端应用部署在 https://www.example.com,则在服务器端设置 Access - Control - Allow - Origin: https://www.example.com

  2. 动态 CORS 配置 对于一些需要支持多个合法源的情况,可以根据请求头中的 Origin 字段进行动态配置。例如,在 Node.js 中使用 Express 实现动态 CORS 配置:

    const express = require('express');
    const app = express();
    
    const allowedOrigins = ['https://www.example.com', 'https://app.example.com'];
    
    app.use((req, res, next) => {
      const origin = req.headers.origin;
      if (allowedOrigins.includes(origin)) {
        res.setHeader('Access - Control - Allow - Origin', origin);
      } else {
        res.status(403).send('Forbidden');
      }
      res.setHeader('Access - Control - Allow - Methods', 'GET, POST, PUT, DELETE');
      res.setHeader('Access - Control - Allow - Headers', 'Content - Type, Authorization');
      next();
    });
    
    // 其他路由和处理逻辑
    const port = 3000;
    app.listen(port, () => {
      console.log(`Server running on port ${port}`);
    });
    

(二)结合反向代理

在生产环境中,可以结合反向代理服务器(如 Nginx 或 Apache)来处理跨域问题。以 Nginx 为例,假设前端应用部署在 https://www.example.com,后端 API 服务器在 http://api.example.com。可以在 Nginx 配置文件中添加如下配置:

server {
    listen 443 ssl;
    server_name www.example.com;

    location / {
        # 前端应用的静态文件处理
        root /path/to/frontend/dist;
        try_files $uri $uri/ /index.html;
    }

    location /api {
        proxy_pass http://api.example.com;
        proxy_set_header Host $host;
        proxy_set_header X - Real - IP $remote_addr;
        proxy_set_header X - Forwarded - For $proxy_add_x_forwarded_for;
        proxy_set_header X - Forwarded - Proto $scheme;
    }
}

上述配置中,/api 路径的请求会被代理到 http://api.example.com,并且设置了一些代理相关的请求头。这样,前端应用通过 /api 路径访问后端 API 时,不会触发跨域问题,因为请求是从同一域名(www.example.com)发出的。同时,反向代理服务器还可以提供负载均衡、缓存等功能,提升应用的性能和稳定性。

五、处理跨域问题的注意事项

  1. 安全风险

    • 在使用 CORS 时,要严格控制 Access - Control - Allow - Origin 的值,避免设置为 * 导致安全漏洞。特别是在生产环境中,要确保只允许信任的源进行跨域访问。
    • 对于 JSONP,由于它通过插入脚本的方式获取数据,要防止恶意脚本注入。尽量避免在不可信的环境中使用 JSONP,或者对返回的数据进行严格的验证和过滤。
  2. 性能影响

    • 预检请求会增加额外的网络开销,尤其是在频繁发送非简单请求的情况下。在设计 API 时,尽量使请求满足简单请求的条件,减少预检请求的发生。例如,避免使用过于复杂的自定义请求头,尽量使用标准的 Content - Type
    • 代理服务器虽然在开发环境中很方便,但在生产环境中,如果代理服务器配置不当,可能会影响性能。例如,代理服务器的缓存设置不合理可能导致数据更新不及时,或者代理服务器本身成为性能瓶颈。
  3. 兼容性

    • JSONP 虽然兼容性较好,但它只支持 GET 请求,并且在现代浏览器中逐渐被 CORS 替代。在选择跨域解决方案时,要考虑项目的目标浏览器和应用场景。如果项目需要兼容一些较老的浏览器,可能需要同时支持 JSONP 和 CORS。
    • CORS 在现代浏览器中得到了广泛支持,但在一些非常老的浏览器(如 IE9 及以下)中不支持。如果项目需要兼容这些老浏览器,可能需要采用其他的解决方案,或者提供降级处理。
  4. 开发与生产环境的差异

    • 在开发环境中使用代理服务器解决跨域问题很方便,但在生产环境中需要采用更正式的解决方案,如 CORS 配置或反向代理。在项目部署过程中,要确保开发环境和生产环境的跨域配置能够顺利切换,避免出现配置不一致导致的问题。
    • 在开发环境中进行跨域测试时,要模拟真实的生产环境场景,包括请求头、请求方法等,以确保在生产环境中不会出现意外的跨域问题。

通过深入理解跨域问题的本质,掌握常见的跨域处理方法,并注意生产环境中的策略和注意事项,我们可以在 Vue 项目中优雅地处理跨域问题,为用户提供安全、稳定且高性能的前端应用。无论是选择 CORS、JSONP 还是代理服务器,都需要根据项目的具体需求和场景进行合理的选择和配置。在实际开发过程中,不断优化跨域处理方案,也是提升前端应用质量的重要环节。