跨域问题常见解决方法

172 阅读4分钟

产生跨域的原因

浏览器的同源策略(即同域名同协议同端口).

如何解决跨域问题

JSONP

可以通过在服务器端动态生成script标签,将需要跨域请求的数据以callback的形式返回给客户端,从而绕过浏览器的同源策略限制,实现跨域请求,客户端无法直接访问返回的数据,只能通过回调函数来获取数据。 服务器端需要返回一段特定的回调函数代码,服务器端在返回数据时将数据作为参数传递给这个回调函数,这段代码会在获取到数据后被调用执行。

 var url = 'https://xxx.com/getData?callback=?' + new Date().getTime();
 var script = document.createElement('script'); 
 script.src = url; 
 document.body.appendChild(script); 

JSONP只支持GET请求,JSONP是通过script标签的src属性进行跨域请求的,无法发送请求头和表单数据块。因此,JSONP主要用于获取数据,而不是用于向服务器发送数据。

本地使用代理:

原理:通过代理服务器来转发跨域请求,相当于在前端项目和后端API之间设置了一个中转站,由代理服务器来处理跨域请求并返回结果。代理服务器需要配置changeOrigin选项,以便允许前端请求的接口跨域访问目标服务器。

独立配置

  1. 安装并启动代理服务器 可以使用 Node.js 安装并启动一个代理服务器,例如:

    npm install -g http-proxy-middleware  
    npx http-proxy-middleware ./public ./dist
    

上面的命令会在本地启动一个代理服务器,将 ./public 目录下的请求代理到 ./dist 目录下。

  1. 在前端代码中使用代理 在前端代码中,可以使用浏览器提供的 fetch 或者 XMLHttpRequest 来进行网络请求。在发起请求时,将请求的 URL 修改为代理服务器的地址即可。例如:

    fetch('http://localhost:8080/api/getData')
        .then(response => response.json()) 
        .then(data => console.log(data)).
        catch(error => console.error(error));
    

上面的代码会将请求发送到代理服务器,代理服务器会将请求转发到目标服务器,并将响应返回给前端。

  1. 配置代理服务器

在代理服务器中,可以配置一些规则来处理特定的请求。例如,可以配置代理服务器将所有以 /api/ 开头的请求代理到另一个服务器上:

npx http-proxy-middleware --target http://xxx.com --changeOrigin --prefix /api

上面的命令将在代理服务器中配置一个规则,将所有以 /api/ 开头的请求代理到 http://xxx.com 上,并且会自动处理跨域问题。

项目中使用,一般脚手架都已集成。

module.exports = { 
    devServer: { 
            proxy: { 
                '/api': { 
                        target: 'http://localhost:8080', // API服务器地址 
                        changeOrigin: true, // 处理跨域请求 
                        pathRewrite: { '^/api': '/api/getData' // 重写路径规则,将请求映射到API服务器的/api/data路径下 
                        } } } } }
                        

后端设置请求头:

原理:通过在服务器端设置HTTP响应头信息,允许浏览器向不同源的服务器发起跨域请求。

const express = require('express'); 
const app = express(); 
app.get('/getData', function(req, res) {
    res.send('try again');
    });
app.use(function(req, res, next) { 
    res.header('Access-Control-Allow-Origin', '*'); 
    next(); 
});

使用XMLHttpRequest或Fetch API发起跨域请求时,需要在请求头中添加Origin字段,以告诉服务器来自哪个来源的请求

const xhr = new XMLHttpRequest(); 
xhr.open('GET', 'http://xxx.com/getData', true); 
xhr.setRequestHeader('Origin', 'http://xxx.com'); 
xhr.onload = function() {
    if (xhr.status === 200) { 
        console.log('success'); 
        } }; 
xhr.send();

使用nginx代理转发

在nginx的配置文件中定义一个名为“api”的location,这个location的作用是拦截所有以“/api”开头的请求

server { 
    listen 80; 
    server_name example.com; 
    location / { 
        root /var/www/html;
        index index.html index.htm; 
} 
    location /api { 
        add_header 'Access-Control-Allow-Origin' '*'; 
        add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS'; 
        add_header 'Access-Control-Allow-Headers' 'Content-Type, X-Requested-With, Accept'; 
    }
}
  • Access-Control-Allow-Origin: 表示允许来自指定源的跨域请求。
  • Access-Control-Allow-Methods: 表示允许的HTTP方法。
  • Access-Control-Allow-Headers: 表示允许的请求头部。

window.domain (已废弃,不推荐)

```
if (window.location.hostname !== "example.com") { window.domain = "example.com"; }
```

window.postMessage

window.postMessage() 方法允许来自一个文档的脚本可以传递文本消息到另一个文档里的脚本

// 在源页面中发送消息 
var message = { "type": "hello", "from": "source" }; 
var origin = "http://xxx.com"; 
window.postMessage(message, origin); 

// 在目标页面中接收消息    
window.addEventListener("message", 
    function(event) { 
    if (event.origin !== origin) { 
        return; 
        } 
    if (event.data.type === "hello") {
        console.log("Received message from source");
        // 在这里处理收到的消息 } 
        });

引申问题

服务端会接收到跨域请求吗(在服务端会发生什么)

简单请求服务端可以正常返回响应,只不过响应被浏览器拦截了而已。复杂请求会先进行预检再确定会不会执行。

简单请求

  • 请求的methods为GET、POST、HEAD
  • 只有最常见的请求头
  • 比较原始的Content-Type

这里可以看这篇 跨域请求被拒绝时,会进入后端接口执行吗,和MDN文档 ,十分详细。