跨域的四种解决方案

6,989 阅读4分钟

一、跨域

跨域: 跨域(Cross-Origin)是指在网络中,一个源(Origin)的网页或者网站尝试去访问另一个不同源的资源时发生的情况。一个“源”是由协议(例如HTTP或HTTPS)、域名和端口号组成的组合。如果任意一个组件不同,就被认为是不同的源。

注意: 协议、域名、端口号中的任意一个不同,就会受到同源策略的限制。

比如: 端口不同导致跨域报错:

image.png

二、四种解决方法

方法1: jsonp(前后端协商)

  • 原理: script标签的src不受到同源策略的限制。但是只能使用get请求。
  • 后端返回一段js代码,代码的内容是一个前端定义好的函数的调用,把需要返回的内容传递进去。(很有意思,后端调用前端定义好的函数,前端定义好的函数必须在全局作用域)
  • 缺点:只能发送get 请求,不安全,不好维护.
  • 优点,兼容性比较好。

1.1 前端

        const jsonp = (callback) => {
        let script = document.createElement("script")
        script.src = `http://localhost:3001/api/jsonp?callback=${callback}`
        document.body.appendChild(script)
        //请求完成 移除script
        script.onload = () => {
          document.body.removeChild(script)
          script = null
        }
        return new Promise((resolve) => {
           // 前端在全局作用域定义一个函数,等待后端返回以后调用该函数,函数的名字需要和传给后端的函数同名
          window[callback] = (data) => {
            resolve(data)
          }
        })
      }
      // 测试,生成的代码需要唯一
      jsonp(`callback${new Date().getTime()}`).then((res) => console.log(res))

1.2 后端

import express from './express'
const arr = express()

app.get('/api/jsonp',(req,res)=>{
    const {callback} = req.query;
    // 返回一段js代码,代码的内容是一个函数的调用,函数的名字是前端的get请求的参数。
    res.send(`${callback}("hello jsonp")`)
})

app.listen(3001,()=>{
    console.log(`server is running at 3001`);
})

方法2: 打包工具自带的服务器代理请求,解决跨域(推荐)

  • 原理: 跨域是浏览器特有的,而服务器和服务器之间不存在跨域。前端的打包工具会自带一个服务器,我们只需要在配置文件中配置反向代理的的规则,然后把请求请求到打包工具的服务器,它就会自动把该请求转发到指定的服务器,当请求完成的时候,它又会把请求的结果返回给浏览器。

以vite为例:

在项目中初始化vite,新建vite.config.ts

$ pnpm i vite

vite.config.ts

// vite.config.ts
import { defineConfig } from "vite"

export default defineConfig({
    server: {
      proxy: {
        "/api": {
          target: `http://localhost:3000`,
          changeOrigin: true,
          rewrite: (path) => path.replace(/^\/api/, ""),
        },
      },
    },
})

解释:

  • target: 所有以/api 开头的地址,都会被代理到target对应的 http://localhost:3000 这个地址。
  • changeOrigin:true : 允许跨域。
  • rewrite : ,接受一个函数,函数的参数就是请求时候的真实的path ,返回值是真实发到对应的目标服务器的path.

以上面的vite 配置为例,举个例子:

image.png

当我们发起请求:http://localhost:5173/api/sayHello (5173 vite默认的端口)的时候,就会被代理到http://localhost:3000/sayHello.

方法3:后端解决(响应头加上指定的字段)

原理: 只要后端在响应头中加上对应的字段,浏览器就不会再报错。一般通过拦截器统一处理。

以express 为例:

app.get("/api/sayHello", (req, res) => {  
    // 允许有所的地址跨域  
    res.setHeader("Access-Control-Allow-Origin", "*")  
    // 允许所有的请求方法  
    res.setHeader("Access-Control-Allow-Methods", "*")  
    // 允许携带cookie  
    res.setHeader("Access-Control-Allow-Credentials", "*")  
    // 允许所有的请求头  
    res.setHeader("Access-Control-Allow-Headers", "*")  
    res.send({ name: "hello word" })  
})  
  
app.listen(3000, () => {  
    console.log("service running at 3000 ...")  
})

如果只想对某一个请求允许跨域,springboot 还可以通过注解实现,前端的装饰器,有时间我再试着写一下前端的装饰器,哈哈哈~~~

方法4: nginx 服务器代理解决跨域。

原理:服务器和服务器之间的通信不存在跨域,因此我们可以开一台中间服务器(nginx),后端无需改变。前端把请求发给nginx , nginx 服务器把请求毫无变化地转发给后端的服务器,后端的服务器响应给 nginx 服务器,nginx 服务器加上响应头以后,再返回给前端。

这样,前端收到的响应就是具有跨域请求响应头的响应了。

image.png

直接再官网下载nginx服务器,解压到本地,找到对应的配置文件配置一下,然后启动nginx服务器就行了。

这里使用微软appStore 中安装的ubuntu 为例子实现。本以为安装很简单,但是安装的时候有一堆问题,几次差点放弃,还好坚持下来了,历尽千辛,记录在这里了# ubuntu 安装遇到的问题(win11)

linux 不是本文的重点,就列举几个常用的linux 命令吧。

$ apt-get install nginx # 安装nginx ,注意换源

$ cat /etc/resolv.conf # 查看IP地址

$ nginx #启动nginx服务 80 默认端口

$ curl http://172.17.16.1:3000/api/sayHello # 发起请求

注意: nginx 默认的端口是80 ,如果windows本地有IIS 服务,或者其它服占用了端口,需要自己更改一下端口。

  1. 安装完成以后,打开ubuntu 安装教程

image.png

依次执行 以下命令,更改nginx 的配置文件。

$ cd /etc/nginx/sites-available #n ginx配置文件的位置
$ ls # 查看当前文件下的所有文件
$ vim default # vim 编辑器没有的话自己安装一下

i 进入编辑模式(左下角的INSERT 代表编辑模式)

image.png

找到对应的配置位置,配置代理(如果代理不生效,可能是80端口被占用了,更改一下)server 中加上配置

server {
	listen 8866 default_server; # 因为我的 80 端口被其它服务占用了,因此改一下
	listen [::]:8866 default_server;

	# include snippets/snakeoil.conf;

	root /var/www/html;

	# Add index.php to the list if you are using PHP
	index index.html index.htm index.nginx-debian.html;

	server_name _;

	location / {
		# First attempt to serve request as file, then
		# as directory, then fall back to displaying a 404.
		try_files $uri $uri/ =404;
	}


       #允许跨域请求的域,* 代表所有
       add_header 'Access-Control-Allow-Origin' *;
       #允许带上cookie请求
       add_header 'Access-Control-Allow-Credentials' 'true';
       #允许请求的方法,比如 GET/POST/PUT/DELETE
       add_header 'Access-Control-Allow-Methods' *;
       #允许请求的header
       add_header 'Access-Control-Allow-Headers' *;
       location /api {
        	proxy_pass http://172.17.16.1:3000; # 吧所有的/api 开头的path 代理到这个位置,和vite 配置类似
        }

}

请求的时候,直接请求到代理服务器,代理服务器会自行转发。