问题引出

我在 http://localhost:8080 放了一台后端服务器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.pushihao.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/hello")
public class HelloController {

@RequestMapping("/h1")
public String h1() {
return "Hello, world!";
}

}

通过调用 http://localhost:8080/hello/h1 可以获得一个字符串

image-20220907131446988

我又在 http://localhost:5173 放了一台前端前端服务器,通过 xhr 请求调用后端的接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<script setup>
import axios from 'axios'
axios.defaults.timeout = 5000

axios.get("http://localhost:8080/hello/h1").then(
response => {
console.log(response)
}, error => {
console.log(error)
}
)
</script>

<template></template>
<style scoped></style>

结果这时,浏览器报错

image-20220907131920563

以上便是 xhr 请求跨域问题的一个经典例子


为什么会有请求跨域问题

什么导致的跨域请求问题?简单来说,浏览器的 同源策略 导致了跨域请求问题。

为什么要制定同源策略?官方是这样解释的:同源策略是一个重要的安全策略,它用于限制一个 origin 的文档或者它加载的脚本如何能与另一个源的资源进行交互。它能帮助阻隔恶意文档,减少可能被攻击的媒介。

什么样的 url 才算是同源呢?当两个 url 的**协议(protocol)域名(host)端口(port)**三者都相同时才算是同源。只要其中有一个不同,那么 url 就不算同源,进行的 http 请求就称为跨域请求。

跨域请求有什么限制?无法读取非同源网页的 cookie、localstorage 等、无法接触非同源网页的 DOM 和 js 对象、无法向非同源地址发送 Ajax 请求。


解决跨域请求问题

我们知道,同源策略是一个重要的安全策略,它可以减少我们被攻击的风险。但是对于正常的请求,我们希望可以接触跨域带来的一些限制。解决跨域请求问题的方法有很多,以下给出一些常用的解决方案。

后端使用 nginx 解决跨域问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
server {
listen 3500;
server_name localhost;
location / {


add_header Access-Control-Allow-Origin *;


add_header Access-Control-Allow-Credentials true;


add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS';


add_header Access-Control-Allow-Headers 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization';


if ($request_method = 'OPTIONS') {
return 204;
}


proxy_pass http://localhost:8080;
}
}

启动 Nginx 服务后,3500 端口被代理到 8080 端口,并且开启了允许所有域进行跨域请求

此时,修改前端请求的 url

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<script setup>
import axios from 'axios'
axios.defaults.timeout = 5000

axios.get("http://localhost:3500/hello/h1").then(
response => {
console.log(response)
}, error => {
console.log(error)
}
)
</script>

<template></template>
<style scoped></style>

启动服务后,请求响应正常

image-20220907144118916


使用前端框架提供的代理服务器

注意:这种方法仅在调试阶段有效,部署到服务器上之后就没用了

使用原理:

只有前端在发送 Ajax 请求时会触发跨域请求,而两台后端服务器进行通信时是不会存在跨域的问题的,因此我们可以在前端与后端之间设置一台代理服务器,这台代理服务器与我们的前端项目保持同源,即:localhost:5173 ,我们的前端项目只负责请求这台代理服务器,由代理服务器向后端接口请求数据。

image-20220907145652664

  1. 对于 webpack + vue 的前端项目,可以使用 devServer 配置代理解决跨域问题

vue.config.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
transpileDependencies: true,









devServer: {
proxy: {
'/api': {
target: 'http://localhost:8080',

pathRewrite: {
'^/api': ''
},
ws: true,



changeOrigin: true
},


'/teacher': {
target: 'http://localhost:8082',
pathRewrite: {
'^/teacher': ''
},
ws: true,
changeOrigin: true
}
},
}
})

AxiosTest.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<script setup>
import axios from 'axios'
axios.defaults.timeout = 5000

// 由于我们制定了重写规则,所以此处的 /api 会在发送请求时被略去
axios.get("/api/hello/h1").then(
response => {
console.log(response)
}, error => {
console.log(error)
}
)
</script>

<template></template>
<style scoped></style>

  1. 对于 vite + vue 的前端项目

vite.config.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'


export default defineConfig({
plugins: [vue()],
server: {
proxy: {
"/api": {
target: "http://localhost:8080",
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, ""),
},
},
},
})

AxiosTest.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<script setup>
import axios from 'axios'
axios.defaults.timeout = 5000

axios.get("/api/hello/h1").then(
response => {
console.log(response)
}, error => {
console.log(error)
}
)
</script>

<template></template>
<style scoped></style>

至此,问题解决!