解决 Spring Boot 应用 CORS 跨域问题的高级配置

解决 Spring Boot 应用 CORS 跨域问题的高级配置:一场与浏览器的爱恨情仇

各位靓仔靓女们,大家好!今天咱们来聊聊一个让无数开发者头疼,却又不得不面对的问题:CORS 跨域! 想象一下,你辛辛苦苦写了一个 Spring Boot 应用,满怀期待地部署到服务器上,结果前端小哥一调用,浏览器直接给你一个大大的红色错误:"No ‘Access-Control-Allow-Origin’ header is present on the requested resource." 是不是感觉瞬间心凉了半截?

别慌!这并不是你的代码有问题,而是浏览器出于安全考虑,启动了 CORS (Cross-Origin Resource Sharing) 机制。 CORS 就像一个严格的门卫,不允许不同源的网页之间随意访问资源。 所谓“同源”,指的是协议、域名和端口都相同。 只要其中一个不同,浏览器就会认为这是跨域请求。

今天,我们就来深入探讨 Spring Boot 应用中 CORS 的高级配置,让你彻底摆脱跨域的困扰,和浏览器握手言和! 准备好了吗? 让我们开始这场与浏览器的爱恨情仇吧!

CORS 到底是个啥?浏览器为啥这么“轴”?

在深入配置之前,我们先简单了解一下 CORS 的原理,这样才能做到知其然,更知其所以然。

浏览器之所以这么“轴”,要归功于它的安全策略——同源策略 (Same-Origin Policy)。 这个策略禁止一个源的文档或脚本访问另一个源的资源。 目的是防止恶意网站通过 JavaScript 读取你的敏感数据,比如 Cookie、LocalStorage 等。

但是,互联网的世界是开放的,不同源的网站之间互相访问资源是不可避免的。 于是,CORS 应运而生。 CORS 是一种机制,它允许服务器告诉浏览器,哪些源可以访问它的资源。

简单来说,CORS 就像一个协议,服务器通过 HTTP 响应头告诉浏览器,哪些“客人”可以进门。 浏览器收到响应后,会检查响应头中的信息,决定是否允许这次跨域请求。

Spring Boot 中解决 CORS 的几种方法

Spring Boot 提供了多种方式来解决 CORS 问题,我们由浅入深,逐一讲解:

1. 使用 @CrossOrigin 注解 (最简单粗暴的方式)

这是最简单的解决 CORS 的方法,只需要在 Controller 或方法上添加 @CrossOrigin 注解即可。

import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@CrossOrigin(origins = "http://localhost:3000") // 允许来自 http://localhost:3000 的跨域请求
public class HelloController {

    @GetMapping("/hello")
    public String hello() {
        return "Hello, CORS!";
    }
}

这段代码表示,只允许来自 http://localhost:3000 的跨域请求访问 /hello 接口。

优点: 简单易用,适用于简单的跨域场景。

缺点: 不够灵活,每个 Controller 或方法都需要添加注解,维护成本较高。 如果你需要允许所有源的访问,可以使用 @CrossOrigin(origins = "*"), 但这在生产环境中通常是不安全的,因为它会暴露你的 API 给所有人。

2. 使用 WebMvcConfigurer 配置 (更灵活的方式)

WebMvcConfigurer 提供了更灵活的 CORS 配置方式,可以在全局范围内控制 CORS 行为。

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("/**") // 允许所有路径的跨域请求
                .allowedOrigins("http://localhost:3000", "http://localhost:4200") // 允许来自这两个源的跨域请求
                .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") // 允许的 HTTP 方法
                .allowedHeaders("*") // 允许所有请求头
                .allowCredentials(true) // 允许发送 Cookie
                .maxAge(3600); // 预检请求的有效期,单位秒
    }
}

这段代码表示,允许来自 http://localhost:3000http://localhost:4200 的跨域请求访问所有路径的 API,允许的 HTTP 方法包括 GET、POST、PUT、DELETE 和 OPTIONS,允许所有请求头,并且允许发送 Cookie,预检请求的有效期为 3600 秒。

优点: 可以全局配置 CORS,更加灵活和可维护。

缺点: 需要编写配置类,稍微复杂一些。

3. 使用 Filter (最高级的配置方式)

使用 Filter 可以对 CORS 进行更细粒度的控制,可以根据不同的请求动态地设置 CORS 响应头。

import org.springframework.stereotype.Component;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Component
public class CorsFilter implements Filter {

    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
        HttpServletResponse response = (HttpServletResponse) res;
        HttpServletRequest request = (HttpServletRequest) req;
        String origin = request.getHeader("Origin"); // 获取请求的 Origin

        // 如果 Origin 不为空,则设置 CORS 响应头
        if (origin != null && !origin.isEmpty()) {
            response.setHeader("Access-Control-Allow-Origin", origin); // 允许来自请求 Origin 的跨域请求
            response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE, PUT"); // 允许的 HTTP 方法
            response.setHeader("Access-Control-Max-Age", "3600"); // 预检请求的有效期
            response.setHeader("Access-Control-Allow-Headers", "*"); // 允许所有请求头
            response.setHeader("Access-Control-Allow-Credentials", "true"); // 允许发送 Cookie
        }

        // 如果是 OPTIONS 请求,则直接返回 200 OK
        if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
            response.setStatus(HttpServletResponse.SC_OK);
        } else {
            chain.doFilter(req, res); // 继续执行请求
        }
    }

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        // 初始化 Filter
    }

    @Override
    public void destroy() {
        // 销毁 Filter
    }
}

这段代码表示,对于每个请求,都先获取请求头的 Origin,然后根据 Origin 动态地设置 CORS 响应头。 如果是 OPTIONS 请求,则直接返回 200 OK,否则继续执行请求。

优点: 可以根据不同的请求动态地设置 CORS 响应头,灵活性最高。

缺点: 代码量较多,需要了解 Servlet Filter 的原理。

CORS 配置详解:深入了解每个参数的含义

在上面的代码示例中,我们使用了一些 CORS 配置参数,下面我们来详细了解一下每个参数的含义:

参数名 含义 示例
allowedOrigins 允许跨域访问的源,可以是一个或多个,也可以是 * (允许所有源)。 allowedOrigins("http://localhost:3000", "http://localhost:4200") allowedOrigins("*")
allowedMethods 允许的 HTTP 方法,可以是一个或多个,常用的方法包括 GET、POST、PUT、DELETE、OPTIONS。 allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
allowedHeaders 允许的请求头,可以是一个或多个,也可以是 * (允许所有请求头)。 allowedHeaders("Content-Type", "Authorization") allowedHeaders("*")
exposedHeaders 允许浏览器访问的响应头,默认情况下,浏览器只能访问一些标准的响应头,如果需要访问自定义的响应头,需要在这里配置。 exposedHeaders("My-Custom-Header")
allowCredentials 是否允许发送 Cookie,如果设置为 true,则浏览器会在跨域请求中携带 Cookie。 注意: 如果 allowCredentials 设置为 true,则 allowedOrigins 不能设置为 *,必须指定具体的源。 allowCredentials(true)
maxAge 预检请求的有效期,单位秒。 浏览器在发送跨域请求之前,会先发送一个 OPTIONS 请求 (预检请求),询问服务器是否允许跨域访问。 如果服务器允许,浏览器才会发送真正的请求。 maxAge 用于指定预检请求的有效期,避免每次都发送预检请求。 maxAge(3600)

重点解释一下 allowCredentials

allowCredentials 用于控制是否允许发送 Cookie。 默认情况下,跨域请求是不允许携带 Cookie 的。 如果你的 API 需要使用 Cookie 进行身份验证,则需要将 allowCredentials 设置为 true

但是! 如果 allowCredentials 设置为 true,则 allowedOrigins 不能设置为 *,必须指定具体的源。 这是因为如果允许所有源发送 Cookie,则可能会导致安全问题。 比如,恶意网站可以通过 JavaScript 读取你的 Cookie,从而冒充你的身份进行操作。

Spring Security 中的 CORS 配置

如果你的 Spring Boot 应用使用了 Spring Security,则需要特别注意 CORS 的配置。 因为 Spring Security 默认会拦截所有请求,包括跨域请求。

有两种方式可以在 Spring Security 中配置 CORS:

1. 使用 http.cors() 配置 (推荐)

Spring Security 提供了 http.cors() 方法,可以方便地配置 CORS。

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;

import java.util.Arrays;

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.cors().configurationSource(corsConfigurationSource()) // 使用自定义的 CORS 配置源
                .and()
                .csrf().disable() // 关闭 CSRF 保护,方便测试
                .authorizeHttpRequests()
                .anyRequest().permitAll(); // 允许所有请求

        return http.build();
    }

    @Bean
    CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration configuration = new CorsConfiguration();
        configuration.setAllowedOrigins(Arrays.asList("http://localhost:3000", "http://localhost:4200")); // 允许来自这两个源的跨域请求
        configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS")); // 允许的 HTTP 方法
        configuration.setAllowedHeaders(Arrays.asList("*")); // 允许所有请求头
        configuration.setAllowCredentials(true); // 允许发送 Cookie
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration); // 允许所有路径的跨域请求
        return source;
    }
}

这段代码表示,使用自定义的 CorsConfigurationSource 来配置 CORS。 CorsConfigurationSource 定义了 CORS 的具体配置,包括允许的源、HTTP 方法、请求头、是否允许发送 Cookie 等。

2. 使用 CorsFilter (不推荐)

你也可以直接使用 CorsFilter 来配置 CORS,但是这种方式不太优雅,不推荐使用。

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;

import java.util.Arrays;

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.csrf().disable() // 关闭 CSRF 保护,方便测试
                .authorizeHttpRequests()
                .anyRequest().permitAll() // 允许所有请求
                .and()
                .addFilter(corsFilter()); // 添加 CorsFilter

        return http.build();
    }

    @Bean
    public CorsFilter corsFilter() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        CorsConfiguration config = new CorsConfiguration();
        config.setAllowedOrigins(Arrays.asList("http://localhost:3000", "http://localhost:4200")); // 允许来自这两个源的跨域请求
        config.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS")); // 允许的 HTTP 方法
        config.setAllowedHeaders(Arrays.asList("*")); // 允许所有请求头
        config.setAllowCredentials(true); // 允许发送 Cookie
        source.registerCorsConfiguration("/**", config);
        return new CorsFilter(source);
    }
}

总结: 在 Spring Security 中配置 CORS,推荐使用 http.cors() 方法,因为它更加简洁和优雅。

CORS 的一些坑和注意事项

  • Access-Control-Allow-Origin 的优先级: 如果服务器返回了多个 Access-Control-Allow-Origin 响应头,浏览器会选择优先级最高的那个。 通常,* 的优先级最低,具体的源的优先级最高。
  • OPTIONS 请求: 浏览器在发送跨域请求之前,会先发送一个 OPTIONS 请求 (预检请求),询问服务器是否允许跨域访问。 因此,你的服务器需要能够正确处理 OPTIONS 请求。
  • Cookie 的安全: 在使用 Cookie 进行身份验证时,一定要注意 Cookie 的安全。 建议使用 HTTPS 协议,并设置 Cookie 的 Secure 属性为 true,防止 Cookie 被窃取。
  • 不同浏览器的差异: 不同浏览器对 CORS 的支持程度可能有所不同。 建议在多个浏览器上进行测试,确保你的 CORS 配置能够正常工作。

总结

解决 Spring Boot 应用的 CORS 跨域问题,就像和浏览器谈恋爱一样,需要了解它的脾气,掌握它的喜好,才能最终抱得美人归。 今天我们学习了三种解决 CORS 的方法:@CrossOrigin 注解、WebMvcConfigurer 配置和 Filter。 我们还深入了解了 CORS 的各个配置参数,以及如何在 Spring Security 中配置 CORS。 希望这些知识能够帮助你彻底摆脱跨域的困扰,让你的 Spring Boot 应用更加健壮和安全。

记住,解决 CORS 问题,不能一蹴而就,需要不断地学习和实践。 只有真正理解了 CORS 的原理,才能灵活地应对各种复杂的跨域场景。 加油! 祝你早日成为 CORS 高手!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注