1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package com.talentcard.server.config;
 
import java.util.Arrays;
import java.util.List;
 
import org.springframework.beans.factory.annotation.Value;
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;
 
@Configuration
public class ApiCorsConfig {
    @Bean
    public CorsFilter apiCorsFilter(
            @Value("${app.cors.allowed-origins:}") String allowedOrigins,
            @Value("${app.cors.allowed-origin-patterns:}") String allowedOriginPatterns) {
        CorsConfiguration cors = new CorsConfiguration();
        cors.setAllowedOrigins(split(allowedOrigins));
        // 本地开发工具的端口是随机的(微信开发者工具每次启动都换一个),写死端口必然漏。
        // 用 origin 通配覆盖本机各种调试端口,真实域名仍走上面的精确白名单。
        cors.setAllowedOriginPatterns(split(allowedOriginPatterns));
        cors.setAllowedMethods(List.of("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"));
        cors.setAllowedHeaders(List.of("Content-Type", "Authorization", "X-Request-Id",
                "X-Client-Platform", "X-Client-Version", "X-Merchant-Store-Id", "X-Idempotency-Key"));
        cors.setMaxAge(3600L);
 
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/api/**", cors);
        // Complete browser preflight before MVC authentication interceptors run.
        return new CorsFilter(source);
    }
 
    private static List<String> split(String values) {
        return Arrays.stream(values.split(",")).map(String::trim).filter(value -> !value.isEmpty()).toList();
    }
}