一篇文章彻底搞懂跨域问题
这真是一个老生常谈的问题 起因是一个工作一年的粉丝出现了跨域问题 跨域答疑 简单描述一下吧: A是后端开发 B是前端开发 A发现B的后台页面调登录接口跨域了,然后A在后端做了CORS,B在前
一篇文章彻底搞懂跨域问题
发布时间:2024-06-26 (2024-06-26)

这真是一个老生常谈的问题

起因是一个工作一年的粉丝出现了跨域问题

跨域答疑

简单描述一下吧:

A是后端开发
B是前端开发

A发现B的后台页面调登录接口跨域了,然后A在后端做了CORS,B在前端设置了前端代理
理论上来说这两个其中一个正常生效,都是没问题的,都不可能出现跨域问题

经过我进一步的盘问,发现他们是在线上服务器环境出现的跨域问题
那就有意思了,线上环境,不都通过nginx反向代理了吗,为啥还会有跨域问题

叫A截了一张图

通过这张图我就发现一个问题

现在前端在8082端口,调接口怎么会调到8088端口去呢

所以敢肯定,前端把接口地址写死了

前端是直接通过axios请求后端接口,没有被nginx匹配到,然后后端设置的CORS要么没有生效,要么写错了,导致出现的跨域

所以解决问题很简单,前端改下axios的baseURL配置,把它去掉或者写服务器的8082地址都可以解决问题

前端把地址写死了

为什么会跨域

跨域问题解决了,但是有没有想过为什么会跨域?

这节课一次性搞懂为什么会跨域以及常见的解决办法

首先跨域是浏览器行为,是浏览器认为不安全进行拦截的,不信你通过curl和apifox去请求,肯定不会有跨域的

首先浏览器有一个策略叫 “同源策略”,它是浏览器最核心也最基本的安全功能

当一个请求url的协议、域名、端口三者之间任意一个与当前页面url不一同,即跨域。

比如上面 前端页面在 8082,去请求8088的后端,那肯定就跨域了。

如何解决跨域

解决跨域有两种方法,一种是代理,另一种是CORS

CORS解决跨域

CORS很简单,就是由后端告诉浏览器,这个请求我授权了是安全的,你放行吧

CORS的设置也很简单,在响应的响应头上加上对应的响应头即可

Access-Control-Allow-Origin: * // 表明接受什么域名
Access-Control-Allow-Credentials: true // 是否允许发送cookie
Access-Control-Expose-Headers: XXX // 是否暴露其他headers值

例如gin中去解决跨域

package main

import (
  "github.com/gin-gonic/gin"
  "net/http"
)

func Cors() gin.HandlerFunc {
  return func(c *gin.Context) {
    method := c.Request.Method
    if c.Request.Header.Get("origin") != "" {
      c.Header("Access-Control-Allow-Origin", "*") // 可将将 * 替换为指定的域名
      c.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE, UPDATE")
      c.Header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, Authorization")
      c.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Cache-Control, Content-Language, Content-Type")
      c.Header("Access-Control-Allow-Credentials", "true")
    }
    if method == "OPTIONS" {
      c.AbortWithStatus(http.StatusNoContent)
    }
    c.Next()
  }
}

func Index(c *gin.Context) {
  c.JSON(200, gin.H{
    "code": 0,
    "msg":  "成功",
    "data": gin.H{},
  })
  return
}

func main() {
  r := gin.Default()
  r.GET("/api/no_cors", Index)
  r.POST("/api/no_cors", Index)
  r.GET("/api/cors", Cors(), Index)
  r.POST("/api/cors", Cors(), Index)
  r.Run(":8080")
}

代理

从同源策略层面,让不同源的请求变成同源的请求

这种方案是目前最主流的跨域解决方案,它分为两类,一个是开发环境,一个是生产环境

开发环境解决跨域

以vue3为例,vite提供了代理功能

import {fileURLToPath, URL} from 'node:url'

import {defineConfig, loadEnv} from 'vite'
import vue from '@vitejs/plugin-vue'
import type {ImportMetaEnv} from "./env";
// https://vitejs.dev/config/
export default defineConfig(({mode}) => {
    let env: Record<keyof ImportMetaEnv, string> = loadEnv(mode, process.cwd())

    const serverUrl =  env.VITE_SERVER_URL
    const wsUrl = serverUrl.replace("http", "ws")
    return {
        plugins: [
            vue(),
        ],
        envDir: "./",
        resolve: {
            alias: {
                '@': fileURLToPath(new URL('./src', import.meta.url))
            }
        },
        server: {
            host: "0.0.0.0",
            port: 80,
            proxy: {
                "/api": {
                    target: serverUrl,
                    changeOrigin: true,
                }
            }
        }
    }
})

凡是使用代理的情况,axios请求的后端路径就不能写死了

因为一旦写死了,代理就捕获不到了,相当于还是前端直接请求后端接口,肯定会跨域的

生产环境解决跨域

使用nginx的反向代理

server {
    listen       80;
    server_name  blog.fengfengzhidao.com;

    location / {
      try_files $uri $uri/ /index.html;  
      root   /opt/gvb/web/dist;
      index  index.html index.htm;
    }

    location /api/ {
      # rewrite ^/(api/.*) /$1 break;
      proxy_set_header Host $host;
      proxy_set_header X-Real-IP $remote_addr;
      proxy_set_header REMOTE-HOST $remote_addr;
      proxy_pass http://127.0.0.1:8082/api/;
    }
    location /uploads/ {
      alias /opt/gvb/server/uploads/;
    }

    access_log  /opt/gvb/access.log;
    error_log   /opt/gvb/error.log;
}