Spring Boot + Vue 项目中的跨域配置与优化

一、跨域问题概述

(一)什么是跨域问题

  1. 定义
  • 跨域问题源于浏览器的 同源策略(Same-Origin Policy)。同源策略是一种重要的安全机制,它限制了来自不同源(Origin)的文档或脚本之间的交互能力。具体来说,如果一个网页的资源(如HTML、JavaScript、CSS等)来自一个源(例如http://example.com),那么它只能与来自同一源的资源进行交互。如果尝试访问来自另一个源(例如http://another-example.com)的资源,就会触发跨域问题。
  • 的定义包括:协议(httphttps)、域名(example.comsub.example.com)、端口号(808080)。只要这三者中有一个不同,就被视为不同源。
  1. 背景
  • 在现代Web开发中,前后端分离架构非常常见。前端项目通常运行在本地开发服务器(如http://localhost:8080),而后端服务可能运行在另一个端口(如http://localhost:8789)或完全不同的域名上。在这种情况下,前端代码尝试向后端服务发送请求时,就会遇到跨域问题。
  1. 示例
  • 假设前端项目运行在http://localhost:8080,后端服务运行在http://localhost:8789。当前端代码中使用axios或其他HTTP客户端向http://localhost:8789/api/data发送请求时,浏览器会阻止该请求,并在控制台中报错:

    Access to XMLHttpRequest at 'http://localhost:8789/api/data' from origin 'http://localhost:8080' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
    
  • 这是因为后端服务没有明确告诉浏览器它允许来自http://localhost:8080的请求。

(二)跨域问题的表现

  1. 常见的错误信息
  • Access to XMLHttpRequest at 'http://example.com/api/data' from origin 'http://localhost:8080' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.:表示后端没有返回Access-Control-Allow-Origin头,浏览器拒绝了请求。
  • Access to fetch at 'http://example.com/api/data' from origin 'http://localhost:8080' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.:表示预检请求(OPTIONS请求)失败。
  • Access to XMLHttpRequest at 'http://example.com/api/data' from origin 'http://localhost:8080' has been blocked by CORS policy: The 'Access-Control-Allow-Origin' header contains multiple values 'http://localhost:8080, *', but only one is allowed.:表示Access-Control-Allow-Origin头的值不合法,不能同时包含多个值。
  1. 影响
  • 跨域问题会导致前端无法正常获取后端数据,影响页面的渲染和功能实现。
  • 如果不解决跨域问题,前端项目在开发阶段和生产环境中都无法正常工作。

(三)跨域问题的解决思路

  1. 后端解决
  • 后端可以通过设置响应头,允许特定的前端域名或所有域名访问其资源。这是最常用和推荐的解决方案,因为它将跨域控制逻辑集中在后端,前端不需要做额外的处理。
  1. 前端解决
  • 前端可以通过配置代理,将请求转发到后端服务。这种方法主要适用于开发阶段,生产环境中通常不建议使用前端代理。
  1. 其他解决方案
  • JSONP(仅支持GET请求):通过动态创建<script>标签来绕过跨域限制,但这种方法存在安全风险,且功能有限。
  • WebSocket:WebSocket协议不受同源策略限制,因此可以用于跨域通信,但仅适用于实时通信场景。

二、后端(Spring Boot)跨域配置

(一)使用 @CrossOrigin 注解

  1. 基本用法
  • @CrossOrigin注解是Spring MVC提供的一个简单方式,用于在Controller类或方法上声明允许跨域请求。

  • 示例代码:

    @RestController
    public class BasicController {
        @RequestMapping("/user")
        @CrossOrigin("http://localhost:5173")
        public User user() {
            User user = new User();
            user.setName("张三");
            user.setAge(666);
            return user;
        }
    }
    
  • 在这个例子中,@CrossOrigin("http://localhost:5173")表示只允许来自http://localhost:5173的请求访问/user接口。

  1. 高级配置
  • @CrossOrigin注解支持多种配置选项:

    • origins:允许访问的前端域名,可以是一个字符串或字符串数组。如果设置为*,表示允许所有域名访问。
    • methods:允许的HTTP方法,如GETPOSTPUTDELETE等。如果未指定,则默认允许所有方法。
    • allowedHeaders:允许的请求头字段。
    • exposedHeaders:允许客户端访问的响应头字段。
    • allowCredentials:是否允许发送Cookie。如果设置为true,则origins不能为*
    • maxAge:预检请求(OPTIONS请求)的缓存时间,单位为秒。
  • 示例代码:

    @RestController
    public class BasicController {
        @RequestMapping("/user")
        @CrossOrigin(origins = "http://localhost:5173", methods = {RequestMethod.GET, RequestMethod.POST}, allowedHeaders = "X-Requested-With", allowCredentials = "true", maxAge = 3600)
        public User user() {
            User user = new User();
            user.setName("张三");
            user.setAge(666);
            return user;
        }
    }
    

(二)全局CORS配置

  1. 通过实现 WebMvcConfigurer 接口
  • 如果希望对整个应用的跨域行为进行统一配置,可以实现WebMvcConfigurer接口并重写addCorsMappings方法。

  • 示例代码:

    @Configuration
    public class CorsConfig implements WebMvcConfigurer {
        @Override
        public void addCorsMappings(CorsRegistry registry) {
            registry.addMapping("/**") // 配置允许跨域的路径,`/**`表示所有路径
                    .allowedOrigins("http://localhost:5173") // 允许的前端域名
                    .allowCredentials(true) // 是否允许发送Cookie
                    .maxAge(3600) // 预检请求的缓存时间,单位为秒
                    .allowedHeaders("*") // 允许的请求头字段
                    .allowedMethods("GET", "POST", "PUT", "DELETE"); // 允许的HTTP方法
        }
    }
    
  • 这种方式的优点是配置集中,便于管理和维护。缺点是不够灵活,无法针对特定接口进行特殊配置。

  1. 配置项说明
  • addMapping:指定需要跨域的路径。
  • allowedOrigins:允许访问的前端域名,可以是一个字符串或字符串数组。如果设置为*,表示允许所有域名访问。
  • allowCredentials:是否允许发送Cookie。如果设置为true,则allowedOrigins不能为*
  • maxAge:预检请求(OPTIONS请求)的缓存时间,单位为秒。
  • allowedHeaders:允许的请求头字段。
  • allowedMethods:允许的HTTP方法。

(三)配置 CorsFilter

  1. 创建 CorsFilter
  • 如果需要更细粒度的跨域控制,可以创建一个CorsFilter。通过CorsConfigurationUrlBasedCorsConfigurationSource来配置CORS,并返回CorsFilter对象。

  • 示例代码:

    @Configuration
    public class CorsFilterConfig {
        @Bean
        public CorsFilter corsFilter() {
            UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
            CorsConfiguration config = new CorsConfiguration();
            config.setAllowCredentials(true); // 是否允许发送Cookie
            config.addAllowedOrigin("http://localhost:5173"); // 允许的前端域名
            config.addAllowedHeader("*"); // 允许的请求头字段
            config.addAllowedMethod("*"); // 允许的HTTP方法
            source.registerCorsConfiguration("/**", config); // 配置允许跨域的路径
            return new CorsFilter(source);
        }
    }
    
  1. 配置项说明
  • CorsConfiguration:用于配置CORS的具体规则。
  • UrlBasedCorsConfigurationSource:用于将CORS配置与URL路径关联起来。
  • CorsFilter:最终生成的过滤器,会在请求到达Controller之前检查跨域规则。

(四)手动设置响应头

  1. 在Controller中设置
  • 在Controller方法中,可以通过HttpServletResponse手动添加Access-Control-Allow-Origin响应头来允许特定的前端域名访问。

  • 示例代码:

    @RestController
    public class BasicController {
        @RequestMapping("/index")
        public String index(HttpServletResponse response) {
            response.addHeader("Access-Control-Allow-Origin", "http://localhost:5173");
            response.addHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE");
            response.addHeader("Access-Control-Allow-Headers", "X-Requested-With");
            response.addHeader("Access-Control-Allow-Credentials", "true");
            return "index";
        }
    }
    
  1. 适用场景
  • 这种方式适用于简单的场景,或者需要在特定方法中动态设置跨域规则的情况。但不推荐在大型项目中广泛使用,因为它会增加代码的复杂性和维护成本。

三、前端(Vue)跨域配置

(一)配置代理

  1. 原理
  • 在开发阶段,Vue CLI提供了一个代理功能,可以将前端请求转发到后端服务。这样,浏览器会认为请求是同源的,从而避免跨域问题。
  • 代理的原理是通过Node.js的http-proxy-middleware中间件实现的。当前端发送请求到代理服务器时,代理服务器会将请求转发到目标后端服务,并将响应返回给前端。
  1. 配置方法
  • 在Vue项目的vue.config.js文件中配置代理:

    module.exports = {
        devServer: {
            proxy: {
                '/api': {
                    target: 'http://localhost:8789', // 后端服务的地址
                    changeOrigin: true, // 是否允许跨域
                    pathRewrite: {
                        '^/api': '' // 重写路径,去掉`/api`前缀
                    }
                }
            }
        }
    };
    
  • 在这个例子中,当前端发送请求到http://localhost:8080/api/data时,Vue CLI的开发服务器会将请求转发到http://localhost:8789/data

  1. 适用场景
  • 代理主要适用于开发阶段,用于解决开发环境下的跨域问题。在生产环境中,通常不建议使用前端代理,而是通过后端配置CORS或使用其他方式解决跨域问题。

(二)修改前端请求路径

  1. 路径调整
  • 配置代理后,前端代码中的请求路径需要调整为代理路径。例如,如果代理配置为/api,则请求路径应从http://localhost:8789/data改为/api/data

  • 示例代码:

    axios.get('/api/data')
        .then(response => {
            console.log(response.data);
        })
        .catch(error => {
            console.error(error);
        });
    
  1. 注意事项
  • 确保请求路径与代理配置一致,否则代理不会生效。
  • 在生产环境中,前端代码中的请求路径应直接指向后端服务的实际地址,而不是代理路径。

四、测试与验证

(一)启动服务

  1. 后端服务
  • 启动Spring Boot项目,确保后端服务正常运行。可以通过访问后端接口(如http://localhost:8789/api/data)来验证后端是否正常响应。
  1. 前端服务
  • 启动Vue项目,运行在本地开发服务器(如http://localhost:8080)。可以通过访问前端页面来验证前端是否正常加载。

(二)发起请求

  1. 测试跨域请求
  • 在前端页面中,使用axios或其他HTTP客户端向后端接口发送请求:

    axios.get('/api/data')
        .then(response => {
            console.log(response.data);
        })
        .catch(error => {
            console.error(error);
        });
    
  • 观察浏览器控制台的输出:

    • 如果配置正确,前端应能成功获取后端数据,并在控制台中打印出响应内容。
    • 如果配置错误,浏览器会报错,显示跨域问题的相关错误信息。
  1. 检查响应头
  • 使用开发者工具(如Chrome DevTools)查看响应头,确认后端是否正确返回了CORS相关的头字段,如Access-Control-Allow-OriginAccess-Control-Allow-Methods等。

(三)验证不同场景

  1. 测试不同HTTP方法
  • 分别测试GETPOSTPUTDELETE等HTTP方法,确保后端的跨域配置支持所有需要的方法。
  1. 测试带Cookie的请求
  • 如果需要在请求中携带Cookie(如登录认证),确保allowCredentials配置为true,并且allowedOrigins不能为*

  • 示例代码:

    axios.get('/api/data', { withCredentials: true })
        .then(response => {
            console.log(response.data);
        })
        .catch(error => {
            console.error(error);
        });
    
  1. 测试预检请求(OPTIONS)
  • 浏览器在发送某些复杂请求(如带有自定义头的POST请求)之前,会先发送一个OPTIONS预检请求,以确认后端是否允许该请求。确保后端的跨域配置能够正确处理OPTIONS请求。

五、拓展学习

(一)了解跨域的其他解决方案

  1. JSONP
  • 原理:JSONP(JSON with Padding)是一种古老的跨域解决方案。它的原理是利用<script>标签的src属性不受同源策略限制的特点,通过动态创建<script>标签来加载一个外部的JavaScript文件,并在加载完成后执行回调函数。

  • 示例代码

    function handleResponse(data) {
        console.log(data);
    }
    
    const script = document.createElement('script');
    script.src = 'http://example.com/data?callback=handleResponse';
    document.head.appendChild(script);
    
  • 限制:JSONP仅支持GET请求,且存在安全风险(如XSS攻击),因此不推荐在现代项目中使用。

  1. WebSocket
  • 原理:WebSocket是一种基于TCP的协议,用于实现客户端和服务器之间的双向通信。WebSocket协议不受同源策略限制,因此可以用于跨域通信。

  • 适用场景:适用于需要实时通信的场景,如在线聊天、实时数据推送等。

  • 示例代码

    const socket = new WebSocket('ws://example.com/socket');
    socket.onmessage = function(event) {
        console.log(event.data);
    };
    socket.send('Hello, server!');
    

(二)学习跨域问题的原理

  1. 同源策略
  • 定义:同源策略是一种重要的网络安全机制,用于限制来自不同源的文档或脚本之间的交互能力。它防止恶意网站窃取用户的敏感信息,如Cookie、本地存储等。
  • 原理:浏览器会检查请求的源(协议、域名、端口)是否与当前页面的源一致。如果不一致,则会阻止某些操作,如读取DOM、发送AJAX请求等。
  1. CORS机制
  • 定义:CORS(Cross-Origin Resource Sharing,跨域资源共享)是一种基于HTTP头的机制,允许服务器明确告诉浏览器哪些外部域名可以访问其资源。
  • 工作原理
    • 当浏览器检测到跨域请求时,会先发送一个OPTIONS预检请求到目标服务器,询问服务器是否允许该请求。
    • 服务器在响应中返回CORS相关的头字段,如Access-Control-Allow-OriginAccess-Control-Allow-Methods等。
    • 浏览器根据这些头字段决定是否允许请求继续进行。
  • 关键头字段
    • Access-Control-Allow-Origin:指定允许访问的前端域名,或*表示允许所有域名。
    • Access-Control-Allow-Methods:指定允许的HTTP方法。
    • Access-Control-Allow-Headers:指定允许的请求头字段。
    • Access-Control-Allow-Credentials:是否允许发送Cookie。
    • Access-Control-Max-Age:预检请求的缓存时间,单位为秒。

(三)实践复杂场景下的跨域配置

  1. 使用Spring Security的跨域配置
  • 如果后端项目使用了Spring Security进行安全控制,跨域配置可能会更加复杂。因为Spring Security会拦截请求并进行认证和授权,可能会与CORS配置冲突。
  • 解决方案
    • 在Spring Security的配置中,允许跨域请求的路径。例如:

      @Configuration
      @EnableWebSecurity
      public class SecurityConfig extends WebSecurityConfigurerAdapter {
          @Override
          protected void configure(HttpSecurity http) throws Exception {
              http.cors().and().csrf().disable();
              http.authorizeRequests()
                      .antMatchers("/api/**").permitAll()
                      .anyRequest().authenticated();
          }
      }
      
    • 确保CORS配置在Spring Security之前生效。可以通过调整配置类的加载顺序或使用@Order注解来实现。

  1. 跨域与Cookie管理
  • 如果需要在跨域请求中携带Cookie(如登录认证),需要特别注意以下几点:
    • 后端的CORS配置中,allowCredentials必须设置为true,并且allowedOrigins不能为*
    • 前端在发送请求时,需要设置withCredentials: true
    • 确保Cookie的SameSite属性设置为None,并且Secure属性设置为true(如果使用HTTPS)。
  1. 跨域与GraphQL
  • 如果后端使用了GraphQL,跨域配置可能会有所不同。GraphQL通常使用POST请求发送查询,因此需要确保CORS配置允许POST方法。

  • 示例代码:

    @Configuration
    public class CorsConfig implements WebMvcConfigurer {
        @Override
        public void addCorsMappings(CorsRegistry registry) {
            registry.addMapping("/graphql")
                    .allowedOrigins("http://localhost:5173")
                    .allowCredentials(true)
                    .allowedMethods("POST")
                    .allowedHeaders("*");
        }
    }
    
posted @ 2025-03-28 23:23  软件职业规划  阅读(210)  评论(0)    收藏  举报