在 Spring Boot 中快速处理 CORS 跨域

跨域问题每个人都会遇到,特别是在前后端分离的系统中。

如果你正在使用 Spring Boot 开发后端应用,并且浏览器中遇到了跨域问题。类似于如下:

Access to fetch at 'http://127.0.0.1/demo' from origin 'https://springdoc.cn' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.

但是你又不想去了解跨域、CORS 这些到底是个啥,你只想快速地解决这个问题。

那你可以把如下配置类,添加到你的 Spring Boot 应用中,它可以解决 99% 以上的跨域问题:

// TODO package 声明
import java.time.Duration;
import java.util.Arrays;
import java.util.stream.Stream;

import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.http.HttpHeaders;
import org.springframework.util.StringUtils;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.filter.CorsFilter;

/**
 * 
 * 全局跨域配置
 * 
 */
@Configuration
public class CorsFilterConfiguration {

    @Bean
    public FilterRegistrationBean<CorsFilter> corsFilter() {
        CorsFilter corsFilter = new CorsFilter(request -> {

            String origin = request.getHeader(HttpHeaders.ORIGIN);

            if (!StringUtils.hasText(origin)) {
                // 非跨域请求
                return null;
            }

            CorsConfiguration config = new CorsConfiguration();
            // 允许所有域
            config.addAllowedOrigin(origin);

            // 允许所有请求 Header
            String requestHeders = request.getHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_HEADERS);
            if (StringUtils.hasText(requestHeders)) {
                config.setAllowedHeaders(Stream.of(requestHeders.split(",")).map(String::trim).distinct().toList());
            }
            
            // 默认允许 Javascript 访问的响应头
            config.setExposedHeaders(Arrays.asList("Cache-Control", "Content-Language", "Content-Length", 
                    "Content-Type", "Expires", "Last-Modified", "Pragma"));
            
            // 允许携带凭证
            config.setAllowCredentials(true);
            
            // 允许所有请求方法
            config.setAllowedMethods(Arrays.asList("GET", "HEAD", "POST", "PUT", 
                    "PATCH", "DELETE", "OPTIONS", "TRACE"));
            
            // 预检缓存 30 分钟
            config.setMaxAge(Duration.ofMinutes(30));

            return config;
        });

        FilterRegistrationBean<CorsFilter> registrationBean = new FilterRegistrationBean<>(corsFilter);
        registrationBean.addUrlPatterns("/*"); // 拦截所有请求
        registrationBean.setOrder(Ordered.HIGHEST_PRECEDENCE); // 最先执行
        return registrationBean;
    }
}

简单说几个要点。

如果你使用这个配置类,那么就没必要使用 Spring / Spring Security 提供的其他跨域解决方案了,例如:WebMvcConfigurer 接口的 addCorsMappings 配置方法、@CrossOrigin 注解。

该配置类,第一行没有 package 声明,你要自己添加。它应该在你的 @SpringBootApplication 子包下,以便被 Spring Boot 自动扫描加载。

如,放到 cn.springdoc.demo.config 包下:

src/main/java
|-cn.springdoc.demo.config
    |-CorsFilterConfiguration.java
|-cn.springdoc.demo
    |-DemoAppplication.java // @SpringBootApplication 类

注意:代码中的 config.setExposedHeaders(...) 方法,设置了允许被 Javascript 访问的响应头。如果你响应了额外的 Header,且需要被客户端访问。那么需要手动添加。

例如:你的登录接口在登录成功后会响应包含了 Token 的 Header:X-Auth-Token,客户端需要访问这个 Header 来读取、保存 Token。你可以在新的一行中使用 addExposedHeader 方法添加:

config.addExposedHeader("X-Auth-Token");

最后,如果你有兴趣了解一下关于 CORS 跨域的详细信息,推荐阅读《Spring 和 CORS 跨域》