谈谈跨域问题

跨域

一、什么是跨域?

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

同源的定义 :如果两个 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:3000 或 http://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.js 或 vite.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请求反向代理到后端,静态资源直接服务。由于同源,自然没有跨域问题。
相关推荐
孙启超10 小时前
【AI开发之Rust】第 22 课:一键多平台与工程收尾 —— CI、发布检查与结课
开发语言·后端·rust
xcl092515 小时前
北京24小时自助健身房系统软件开发实战:从架构到部署全流程指南
java·spring boot·架构
一 乐17 小时前
动漫书销售商城|基于springboot + vue动漫书销售商城(源码+数据库+文档)
java·数据库·vue.js·spring boot·毕业设计
卓怡学长17 小时前
w214基于jsp知道特产网
java·intellij-idea
步行cgn17 小时前
Spring 注解使用详解
java·spring
其实防守也摸鱼17 小时前
Fastjson 反序列化漏洞(JNDI注入)
android·数据库·安全·oracle·自动化·fastjson·反序列化
一条小小yu17 小时前
Spring IoC的理解
java·后端·spring
落魄实习生18 小时前
Agent Scope Java 2.x 系列【7】工具使用
java·开发语言·ai
旺仔学长 哈哈18 小时前
springboot钓鱼爱好者交流平台APP设计与实现
java·spring boot·mysql·充电桩管理系统
茉莉玫瑰花茶18 小时前
GO [ 方法 ]
开发语言·后端·golang