跨域解决方法

129 阅读4分钟

🌐 什么是跨域问题?

在 Web 开发中,跨域(Cross-Origin)是一个常见的问题。它通常发生在前端(如 Vue、React、HTML 页面)向后端发送请求时,如果前后端的 协议、域名(或 IP 地址)、端口 有一个不一样,就会触发浏览器的同源策略(Same-Origin Policy),导致请求被拦截。

🔍 举个例子:

假设你的前端项目运行在:

http://localhost:8080

你的后端接口地址是:

http://localhost:3000

虽然它们都在 localhost 上,但端口不同(8080 vs 3000),浏览器就会认为它们是不同源的,从而阻止请求,这就是跨域问题。


🔐 浏览器的同源策略

浏览器为了保护用户的安全,制定了一个规则:只允许前端访问同源的后端接口

URL 解析:http://localhost:3000/api

组成部分描述示例中的值
协议(Protocol)定义了客户端和服务器之间通信的方式。常见的协议有 HTTP 和 HTTPS。http
域名(Domain)或 IP 地址域名是更易记的网站地址形式,而 IP 地址则是实际的网络地址。在这个例子中,localhost 是一个特殊域名,代表本机。localhost
端口(Port)端口号标识了特定主机上的具体服务。不同的服务可能运行在同一个服务器的不同端口上。如果不指定,默认 HTTP 使用 80 端口,HTTPS 使用 443 端口。3000
路径(Path)路径指定了请求的具体资源位置。它可以表示 API 的某个端点或者网页的一个具体页面。/api

同源是指:协议、域名(或 IP)、端口 三者必须完全相同。

举例是否同源说明
http://localhost:3000http://localhost:3000/api✅ 是协议、域名、端口都相同
http://localhost:3000https://localhost:3000/api❌ 否协议不同(http vs https)
http://localhost:3000http://127.0.0.1:3000/api❌ 否域名不同(localhost vs 127.0.0.1)
http://localhost:3000http://localhost:8080/api❌ 否端口不同(3000 vs 8080)

🚫 为什么会有跨域问题?

跨域问题的根源是浏览器的安全机制。比如你正在访问一个银行网站,如果你的浏览器允许任意网站向银行服务器发送请求,就可能被恶意网站利用,窃取你的账户信息。

所以,浏览器默认会阻止跨域请求,保护用户数据安全。


一、CORS

✅ 什么是 CORS?

CORS(Cross-Origin Resource Sharing,跨域资源共享)是一种标准机制,允许服务器设置响应头,告诉浏览器:“这个前端网站可以访问我的接口。”

也就是说,解决跨域问题的关键在于后端,不是前端。

✅ 如何配置 CORS?

原理:后端在响应头中设置允许的源,方法,头信息,来告知浏览器不需要同源策略的保护。 在 Koa 中,我们可以使用 @koa/cors 插件来快速启用 CORS 支持。

1. 安装插件:

npm install @koa/cors

2. 在主文件中引入并使用:

const cors = require('@koa/cors')
app.use(cors())

这样就允许所有来源访问你的接口了。

3. 可选:限制允许访问的域名

如果你只想让特定的前端网站访问接口,可以这样配置:

app.use(cors({
  origin: 'http://localhost:8080' // 只允许这个前端访问
}))

二.用jsonp的方式解决跨域(只适用于 GET 请求,不推荐现代项目使用。)

JSONP(JSON with Padding)是一种利用 <script> 标签不受同源策略限制的特性,实现跨域请求的“古老但有效”的方法。 在网页开发中,我们允许 HTML 中的 <script><img><link> 等标签从不同的域名(即“跨域”)加载资源,这是浏览器默认支持的行为,目的是为了让 Web 更加灵活、高效和实用。

  function jsonp(url,callback) {
            const script = document.createElement('script')
            script.src = `${url}?callback=${callback}`
            document.body.appendChild(script)
        }
        jsonp('http://localhost:3000','33')
        
        
const http = require('http')

  1. 创建一个 <script> 标签
    动态创建一个 script 元素,准备加载外部 JS 文件。
  2. 设置请求地址,并带上回调函数名
    URL 变成:
    http://localhost:3000?callback=handleData
    这是在告诉服务器:“请把数据用 handleData(...) 这个函数包起来返回。”
  3. 插入页面,触发加载
    一旦 script 被添加到页面,浏览器就会自动发起请求,加载并执行返回的 JavaScript 代码。

🔥 关键点:<script> 加载的是“可执行的代码”,而不是“原始数据”。浏览器不会阻止它执行。

const server = http.createServer((req, res) => {
   const query = new URL(req.url,`http://${req.headers.host}`).searchParams
   const callback = query.get('callback')
   const data = {
       name:'张三',
       age:18
   }
    res.end(`${callback}(${JSON.stringify(data)})`)
    console.log(callback)
    })
    server.listen(3000, () => {
    console.log('server is running')
    })

后端收到请求后:

  1. 从 URL 中提取 callback=handleData
  2. 准备好数据 { name: '张三', age: 18 }
  3. 返回一段 JavaScript 代码handleData({"name":"张三","age":18})

我们知道,浏览器有同源策略,它会阻止 JavaScript 通过 fetch 或 XMLHttpRequest 这样的方式,去请求不同域名、协议或端口的数据。但HTML 中的 <script><img><link>,资源加载是不受同源策略限制的。也就是说,我们可以用 <script src="..."> 去加载任何域名下的 JavaScript 文件。

JSONP 就是巧妙地利用了 <script> 标签这个“漏洞”来实现跨域通信的。它的核心思想是把数据包装成一段 JavaScript 函数调用,通过 script 标签加载并执行,从而把数据“送回来”。

具体流程是这样的:前端定义一个全局函数,比如叫 handleData,然后动态创建一个 <script> 标签,把它的 src 指向后端接口,并在 URL 后面加上一个 callback 参数。

后端收到这个请求后,会解析出 callback 参数的值,然后把要返回的数据,比如 { name: '张三', age: 18 },用 JSON.stringify 转成字符串,再拼接成一段 JavaScript 代码,作为响应体返回。

所以,JSONP 的本质是让服务器把数据变成一个函数调用,通过 script 标签这个“合法通道”传递过来,再通过函数执行把数据接住。这种方式巧妙地绕过了同源策略对 AJAX 请求的限制,实现了跨域数据通信。

当然,JSONP 也有一些局限,比如只支持 GET 请求、有 XSS 风险、错误处理困难等,所以在现代开发中更多使用 CORS 或代理。但在一些老项目或第三方接口调用中,JSONP 仍然是一种简单有效的解决方案。

三、开发代理(Dev Proxy)——开发阶段推荐 ✅

在开发环境中,可以通过构建工具的“代理”功能,将请求转发到真实后端,从而避免跨域。

示例:Vite 配置代理

// vite.config.js
export default {
  server: {
    proxy: {
      '/api': {
        target: 'http://localhost:3000', // 后端服务地址
        changeOrigin: true,              // 支持跨域
        rewrite: (path) => path.replace(/^/api/, '') // 重写路径
      }
    }
  }
}

前端请求方式:

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

👉 实际请求会被代理到:http://localhost:3000/users

✅ 优点:

  • 开发阶段无需后端修改 CORS
  • 配置简单,集成方便

❌ 缺点:

  • 仅在开发环境有效
  • 生产部署仍需后端或 Nginx 配合

四、Nginx 反向代理 —— 生产环境最佳实践 ✅✅

将前端和后端部署在同一域名下,通过 Nginx 统一入口,彻底规避跨域问题。

Nginx 配置示例:

nginx
编辑
server {
  listen 80;
  server_name yourapp.com;

  # 前端静态资源
  location / {
    root /var/www/frontend;
    try_files $uri $uri/ /index.html;
  }

  # 后端 API 代理
  location /api/ {
    proxy_pass http://localhost:3000/;  # 实际后端地址
    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;
  }
}

效果说明:

  • 前端访问:https://yourapp.com
  • 请求接口:https://yourapp.com/api/users
  • Nginx 自动将 /api/* 请求转发给后端服务

✅ 优点:

  • 彻底解决跨域
  • 提高性能(缓存、压缩)
  • 安全可控(SSL、限流、鉴权)

🚀 推荐用于生产环境部署!