谈谈跨域问题

跨域

一、什么是跨域?

跨域问题是由浏览器的 同源策略 引起的。同源策略是浏览器一个重要的安全机制,它用于限制一个源的文档或脚本如何与另一个源的资源进行交互。

同源的定义 :如果两个 URL 的协议(Protocol)、域名(Host)、端口(Port) 都完全相同,则这两个 URL 是同源的。

当前页面URL 目标URL 是否同源 原因
https://www.example.com/index.html https://www.example.com/api/user 协议、域名、端口均相同
https://www.example.com/index.html http://www.example.com/api/user 协议不同 (https vs http)
https://www.example.com/index.html https://api.example.com/user 域名不同 (www vs api)
https://www.example.com:8080/index.html https://www.example.com/api/user 端口不同 (8080 vs 默认443)

只要有一个不同,就是跨域 。浏览器会阻止跨域的 AJAX请求 返回的数据被页面 JavaScript 获取,但对于像 <img>, <script>, <link>, <iframe> 等标签的资源引用,浏览器是允许的。

这里讲讲 AJAX请求A synchronous J avaScript A nd XML

AJAX 是 A synchronous J avaScript A nd XML 的缩写。

  • Asynchronous(异步): 这是 AJAX 的灵魂。当你向服务器发送请求后,JavaScript 代码不会傻傻地等待服务器响应,而是可以继续执行其他任务。当服务器响应返回时,JavaScript 再回过头来处理这个响应。这使得用户体验非常流畅,不会出现页面"卡死"的情况。
  • JavaScript: 整个过程的"大脑"。它负责发送请求、监听响应、并最终用返回的数据更新页面。
  • XML : 最初,AJAX 技术中服务器返回的数据格式通常是 XML。但现在更常用的是 JSON(JavaScript Object Notation)格式,因为它更轻量、更容易被 JavaScript 解析。所以,虽然名字里有 XML,但现在 AJAX 请求的数据格式可以是 JSON、XML、纯文本或 HTML。

AJAX 是一种用于创建快速、动态网页的技术。它允许网页在不重新加载整个页面的情况下,与服务器交换数据并更新部分网页内容。

可以把它想象成点餐的过程:

  • 传统方式(无 AJAX): 每点一道菜,你就把整本菜单(整个页面)送回厨房(服务器),厨房做好一道菜后,把一本全新的菜单(整个新页面)连同这道菜一起给你。效率极低。
  • AJAX 方式: 你有一个智能服务员(AJAX)。你点菜(触发事件),服务员悄悄地去厨房(服务器)下单,厨房只做好这一道菜(数据),服务员只把这道菜端给你(局部更新页面)。你面前的餐桌(页面其他部分)完全不受影响。

二、为什么需要同源策略?(安全原因)

没有同源策略会非常危险。想象一下这个场景:

  1. 你登录了网上银行,网站 https://my-bank.com 将你的身份认证信息(如 Cookie)保存在你的浏览器中。
  2. 然后,你不小心访问了一个恶意网站 https://evil-site.com
  3. 这个恶意网站的 JavaScript 尝试向 https://my-bank.com/transfer 发起一个 AJAX 请求,要求转账给攻击者。
  4. 浏览器会自动带上你在 my-bank.com 的 Cookie。
  5. 如果没有同源策略,恶意网站就能拿到银行网站的响应,甚至完成转账操作。

同源策略有效地防止了这种"跨站请求伪造"等攻击,保护了用户的数据安全。

三、跨域问题再现

典型开发环境:

  • 后端 Java 服务 :运行在 http://localhost:8080
  • 前端 Vue 开发服务器 :运行在 http://localhost:3000http://localhost:5173 (Vite)

当 Vue 页面中的 Axios 尝试请求 http://localhost:8080/api/users 时,由于端口不同,浏览器会触发同源策略,阻止请求,并在控制台报错:Access to XMLHttpRequest at 'http://localhost:8080/api/users' from origin 'http://localhost:3000' has been blocked by CORS policy

四、解决方案一:后端解决(配置 CORS)【主流方案】

这是最规范、最生产环境友好的方案。核心思想是让 Java 后端在响应头中添加允许跨域的字段

1. 使用 @CrossOrigin 注解(简单方便)

在 Spring Boot 的 Controller 类或方法上添加注解,适用于快速测试或粒度控制。

java 复制代码
@RestController
@RequestMapping("/api")
// 在类上使用,对该控制器所有方法生效
@CrossOrigin(origins = "http://localhost:3000") // 允许特定源
public class UserController {

    @GetMapping("/users")
    // 也可以在方法上使用,更细粒度控制
    @CrossOrigin(origins = "http://localhost:3000") 
    public List<User> getUsers() {
        // ... 业务逻辑
        return userService.findAll();
    }

    @PostMapping("/users")
    // 允许所有源(生产环境慎用)
    @CrossOrigin(origins = "*") 
    public User createUser(@RequestBody User user) {
        return userService.save(user);
    }
}

优缺点:

  • :配置简单,一目了然。
  • :多个 Controller 需要重复配置,不够集中;无法处理复杂需求(如允许自定义头、缓存预检请求等)。

2. 实现 WebMvcConfigurer 接口(推荐,全局配置)

通过 Java 配置类进行全局配置,这是最常用和灵活的方式。

java 复制代码
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class CorsConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/api/**") // 配置针对哪些API路径
                .allowedOrigins("http://localhost:3000", "https://www.your-app.com") // 允许的源
                .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") // 允许的HTTP方法
                .allowedHeaders("*") // 允许所有的请求头
                .allowCredentials(true) // 允许发送Cookie等凭证
                .maxAge(3600); // 预检请求的缓存时间(秒),减少OPTIONS请求
    }
}

关键配置解释:

  • allowedOrigins:生产环境应替换为 Vue 应用的实际部署域名,而不是 *
  • allowCredentials(true):如果前端请求需要携带 Cookie 或 Authorization 头,此项必须为 true,且 allowedOrigins 不能为 *
  • maxAge:设置预检请求结果的缓存时间,提升性能。

3. 使用 CorsFilter(更底层,功能强大)

对于非 Spring MVC 项目或需要更精细控制的情况,可以配置自定义 Filter。

java 复制代码
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;

import java.util.Arrays;

@Configuration
public class CustomCorsFilter {

    @Bean
    public FilterRegistrationBean<CorsFilter> corsFilter() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        CorsConfiguration config = new CorsConfiguration();
        
        config.setAllowCredentials(true);
        // 明确设置允许的源,而不是使用"*"
        config.setAllowedOrigins(Arrays.asList("http://localhost:3000", "https://www.your-app.com"));
        config.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS"));
        config.setAllowedHeaders(Arrays.asList("*")); // 或明确指定:"Authorization", "Content-Type"
        
        // 针对所有路径应用此CORS配置
        source.registerCorsConfiguration("/**", config);
        
        FilterRegistrationBean<CorsFilter> bean = new FilterRegistrationBean<>(new CorsFilter(source));
        bean.setOrder(0); // 设置过滤器最高优先级
        return bean;
    }
}

五、解决方案二:前端解决(开发环境代理)

这个方案主要在开发阶段使用,利用 Vue CLI 或 Vite 内置的代理功能,将 API 请求"转发"到后端服务器。因为代理服务器和 Vue 应用本身是同源的,所以浏览器不会报跨域错误。

1. Vue CLI (Webpack Dev Server) 配置

在项目根目录下的 vue.config.js 文件中配置:

java 复制代码
// vue.config.js
const { defineConfig } = require('@vue/cli-service')

module.exports = defineConfig({
  transpileDependencies: true,
  devServer: {
    port: 3000,
    proxy: {
      // 代理所有以 '/api' 开头的请求
      '/api': {
        target: 'http://localhost:8080', // 你的Java后端地址
        changeOrigin: true, // 改变请求头中的Origin为目标URL
        pathRewrite: {
          '^/api': '' // 重写路径,去掉 '/api' 前缀
          // 例如:前端请求 /api/users -> 代理到 http://localhost:8080/users
        }
      }
    }
  }
})

前端 Vue 组件中的请求示例:

java 复制代码
// 在Vue组件中,直接请求相对路径 '/api/...'
import axios from 'axios';

export default {
  methods: {
    async fetchUsers() {
      try {
        // 开发阶段:请求被代理到 -> http://localhost:8080/api/users
        // 生产阶段:请求直接发往 -> https://your-api-domain.com/api/users (需要配置生产环境BASE_URL)
        const response = await axios.get('/api/users');
        this.users = response.data;
      } catch (error) {
        console.error('获取用户数据失败:', error);
      }
    }
  }
}

2. Vite 配置

vite.config.jsvite.config.ts 中配置:

java 复制代码
// vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [vue()],
  server: {
    port: 3000,
    proxy: {
      '/api': {
        target: 'http://localhost:8080',
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/api/, '')
      }
    }
  }
})

六、生产环境部署策略

部署方式 跨域解决方案 说明
前后端分离部署 Java 后端配置 CORS 前端(Vue打包后的静态文件)部署在 Nginx/CDN(域A),后端Java应用部署在另一个服务器(域B)。这是最经典的场景,必须由后端配置CORS。
前后端同域部署 无需处理跨域 将Vue打包后的 dist 目录内容放在Java项目的 static 目录下,或使用Nginx将API请求反向代理到后端,静态资源直接服务。由于同源,自然没有跨域问题。
相关推荐
菜鸟plus+2 小时前
MinIO
java
绝无仅有2 小时前
面试复盘:哔哩哔哩、蔚来、字节跳动、小红书面试与总结
后端·面试·github
艾菜籽2 小时前
JVM中的垃圾回收机制
java·jvm
绝无仅有3 小时前
面试经历分享:从特斯拉到联影医疗的历程
后端·面试·github
IT_陈寒3 小时前
JavaScript性能优化:这7个V8引擎技巧让我的应用速度提升了50%
前端·人工智能·后端
敲代码的嘎仔3 小时前
JavaWeb零基础学习Day1——HTML&CSS
java·开发语言·前端·css·学习·html·学习方法
独行soc8 小时前
2025年渗透测试面试题总结-97(题目+回答)
网络·安全·web安全·adb·面试·渗透测试·安全狮
Terio_my8 小时前
Java bean 数据校验
java·开发语言·python
Tony Bai9 小时前
【Go开发者的数据库设计之道】07 诊断篇:SQL 性能诊断与问题排查
开发语言·数据库·后端·sql·golang