从入门到熟悉跨域(CORS)

该文章已生成可运行项目,

从入门到熟悉跨域(CORS)

目录

跨域简介

什么是跨域

跨域(Cross-Origin Resource Sharing, CORS)是指浏览器出于安全考虑,阻止网页向不同源(协议、域名、端口)的服务器发起请求的一种安全机制。当网页试图访问不同源的资源时,浏览器会执行同源策略检查,如果不符合同源策略,则会阻止请求。

同源策略

同源策略要求:
  协议相同: http/https
  域名相同: example.com
  端口相同: 80/443/8080等

跨域请求示例

跨域场景:
  - 前端: http://localhost:3000
    后端: http://localhost:8080 (端口不同)
  
  - 前端: https://www.example.com
    后端: http://api.example.com (协议不同)
  
  - 前端: http://example.com
    后端: http://sub.example.com (子域名不同)

跨域的影响

影响范围:
  - AJAX请求被阻止
  - Fetch API请求失败
  - WebSocket连接失败
  - 图片、字体等资源加载失败
  - 表单提交可能被阻止

跨域流程简介图

简单跨域请求流程

浏览器 服务器 发起跨域请求 检查Origin头 验证请求来源 返回响应(包含CORS头) 浏览器检查CORS头 允许/阻止请求结果 浏览器 服务器

预检请求(Preflight)流程

浏览器 服务器 发送OPTIONS预检请求 检查Origin、Method、Headers 返回预检响应(包含CORS策略) 浏览器验证预检响应 发送实际请求(如果预检通过) 返回实际响应 浏览器 服务器

跨域请求详细流程

浏览器发起跨域请求
是否为简单请求?
直接发送请求
发送OPTIONS预检请求
服务器处理请求
服务器返回预检响应
预检是否通过?
发送实际请求
请求被阻止
服务器返回响应
响应是否包含CORS头?
浏览器允许访问响应
浏览器阻止访问响应
请求成功
请求失败

同源策略检查流程

发起HTTP请求
检查请求URL
检查当前页面URL
协议是否相同?
跨域请求
域名是否相同?
端口是否相同?
同源请求
执行CORS检查
直接发送请求
服务器是否支持CORS?
添加CORS头
请求被阻止
请求成功
请求失败

跨域的类型

1. 简单跨域请求

简单请求的条件
简单请求要求:
  - 请求方法: GET、POST、HEAD
  - 请求头: 只能是简单头部
    - Accept
    - Accept-Language
    - Content-Language
    - Content-Type
    - DPR
    - Downlink
    - Save-Data
    - Viewport-Width
    - Width
  - Content-Type: 
    - application/x-www-form-urlencoded
    - multipart/form-data
    - text/plain
简单请求示例
// 简单跨域请求
fetch('http://api.example.com/users', {
    method: 'GET',
    headers: {
        'Content-Type': 'application/json'
    }
});

// 简单表单提交
const formData = new FormData();
formData.append('name', 'John');
fetch('http://api.example.com/submit', {
    method: 'POST',
    body: formData
});

2. 复杂跨域请求

复杂请求的特征
复杂请求特征:
  - 请求方法: PUT、DELETE、PATCH等
  - 自定义请求头
  - Content-Type不是简单类型
  - 包含认证信息
  - 需要预检请求
复杂请求示例
// 复杂跨域请求
fetch('http://api.example.com/users/123', {
    method: 'PUT',
    headers: {
        'Content-Type': 'application/json',
        'Authorization': 'Bearer token123',
        'Custom-Header': 'custom-value'
    },
    body: JSON.stringify({
        name: 'John Doe',
        email: 'john@example.com'
    })
});

3. 预检请求(Preflight)

预检请求的触发条件
预检请求触发:
  - 非简单请求方法
  - 包含自定义请求头
  - Content-Type不是简单类型
  - 包含认证头
  - 包含其他非简单头
预检请求示例
OPTIONS /users HTTP/1.1
Host: api.example.com
Origin: http://localhost:3000
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: Content-Type, Authorization, Custom-Header
预检响应示例
HTTP/1.1 200 OK
Access-Control-Allow-Origin: http://localhost:3000
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization, Custom-Header
Access-Control-Max-Age: 86400

4. 凭证跨域请求

凭证请求的特征
凭证请求特征:
  - 包含Cookie
  - 包含Authorization头
  - 需要服务器明确允许
  - 不能使用通配符*
凭证请求示例
// 包含凭证的跨域请求
fetch('http://api.example.com/users', {
    method: 'GET',
    credentials: 'include', // 包含Cookie
    headers: {
        'Authorization': 'Bearer token123'
    }
});

重难点分析

1. 安全性问题

CSRF攻击风险
@Component
public class CSRFProtectionService {
  
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
  
    public boolean validateCSRFToken(String token, String sessionId) {
        // 验证CSRF Token
        String key = "csrf:" + sessionId;
        String storedToken = (String) redisTemplate.opsForValue().get(key);
      
        if (storedToken == null || !storedToken.equals(token)) {
            log.warn("CSRF Token验证失败: sessionId={}", sessionId);
            return false;
        }
      
        return true;
    }
  
    public String generateCSRFToken(String sessionId) {
        String token = UUID.randomUUID().toString();
        String key = "csrf:" + sessionId;
      
        // 设置Token过期时间
        redisTemplate.opsForValue().set(key, token, Duration.ofHours(1));
      
        return token;
    }
}
同源策略绕过风险
@Component
public class OriginValidationService {
  
    private final Set<String> allowedOrigins = new HashSet<>();
  
    @PostConstruct
    public void init() {
        // 配置允许的源
        allowedOrigins.add("http://localhost:3000");
        allowedOrigins.add("https://www.example.com");
        allowedOrigins.add("https://app.example.com");
    }
  
    public boolean isValidOrigin(String origin) {
        if (origin == null) {
            return false;
        }
      
        // 检查是否为允许的源
        return allowedOrigins.contains(origin);
    }
  
    public boolean isSubdomainAllowed(String origin) {
        // 检查子域名是否允许
        return origin.endsWith(".example.com");
    }
}

2. 性能问题

预检请求缓存
@Component
public class PreflightCacheService {
  
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
  
    public void cachePreflightResult(String origin, String method, 
                                   Set<String> headers, int maxAge) {
        String key = generateCacheKey(origin, method, headers);
        PreflightCache cache = new PreflightCache();
        cache.setMaxAge(maxAge);
        cache.setTimestamp(System.currentTimeMillis());
      
        redisTemplate.opsForValue().set(key, cache, Duration.ofSeconds(maxAge));
    }
  
    public PreflightCache getPreflightCache(String origin, String method, 
                                          Set<String> headers) {
        String key = generateCacheKey(origin, method, headers);
        return (PreflightCache) redisTemplate.opsForValue().get(key);
    }
  
    private String generateCacheKey(String origin, String method, Set<String> headers) {
        return String.format("preflight:%s:%s:%s", origin, method, 
                           String.join(",", headers));
    }
}
请求频率控制
@Component
public class CORSRequestRateLimiter {
  
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
  
    public boolean isRateLimitExceeded(String origin, String ip) {
        String key = String.format("cors:rate:%s:%s", origin, ip);
      
        // 获取当前请求次数
        Long count = redisTemplate.opsForValue().increment(key);
      
        if (count == 1) {
            // 设置过期时间
            redisTemplate.expire(key, Duration.ofMinutes(1));
        }
      
        // 每分钟最多允许100次跨域请求
        return count > 100;
    }
  
    public void recordCORSRequest(String origin, String ip, String method) {
        String key = String.format("cors:stats:%s:%s", origin, method);
        redisTemplate.opsForValue().increment(key);
        redisTemplate.expire(key, Duration.ofHours(24));
    }
}

3. 兼容性问题

浏览器兼容性处理
@Component
public class BrowserCompatibilityService {
  
    public CORSHeaders getCompatibleHeaders(String userAgent, String origin) {
        CORSHeaders headers = new CORSHeaders();
      
        if (isOldBrowser(userAgent)) {
            // 旧版本浏览器兼容处理
            headers.setAllowOrigin("*");
            headers.setAllowMethods("GET, POST");
            headers.setAllowHeaders("Content-Type");
        } else {
            // 现代浏览器标准处理
            headers.setAllowOrigin(origin);
            headers.setAllowMethods("GET, POST, PUT, DELETE, OPTIONS");
            headers.setAllowHeaders("Content-Type, Authorization, X-Requested-With");
            headers.setAllowCredentials(true);
        }
      
        return headers;
    }
  
    private boolean isOldBrowser(String userAgent) {
        return userAgent.contains("MSIE") || 
               userAgent.contains("Trident") ||
               userAgent.contains("Edge/12");
    }
}

处理方式

1. 服务器端CORS配置

全局CORS配置
@Configuration
public class GlobalCORSConfig {
  
    @Bean
    public CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration configuration = new CorsConfiguration();
      
        // 允许的源
        configuration.setAllowedOriginPatterns(Arrays.asList(
            "http://localhost:*",
            "https://*.example.com",
            "https://app.example.com"
        ));
      
        // 允许的HTTP方法
        configuration.setAllowedMethods(Arrays.asList(
            "GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH"
        ));
      
        // 允许的请求头
        configuration.setAllowedHeaders(Arrays.asList(
            "Origin", "Content-Type", "Accept", "Authorization", 
            "X-Requested-With", "Cache-Control"
        ));
      
        // 允许的响应头
        configuration.setExposedHeaders(Arrays.asList(
            "Access-Control-Allow-Origin", "Access-Control-Allow-Credentials"
        ));
      
        // 是否允许凭证
        configuration.setAllowCredentials(true);
      
        // 预检请求缓存时间
        configuration.setMaxAge(3600L);
      
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);
      
        return source;
    }
}
注解方式配置
@RestController
@RequestMapping("/api/users")
@CrossOrigin(
    origins = {"http://localhost:3000", "https://app.example.com"},
    allowedHeaders = {"Content-Type", "Authorization"},
    methods = {RequestMethod.GET, RequestMethod.POST, RequestMethod.PUT, RequestMethod.DELETE},
    allowCredentials = "true",
    maxAge = 3600
)
public class UserController {
  
    @GetMapping("/{id}")
    public ResponseEntity<User> getUser(@PathVariable Long id) {
        // 实现获取用户逻辑
        return ResponseEntity.ok(new User());
    }
  
    @PostMapping
    public ResponseEntity<User> createUser(@RequestBody User user) {
        // 实现创建用户逻辑
        return ResponseEntity.ok(user);
    }
}

2. 过滤器方式处理

CORS过滤器
@Component
public class CORSFilter implements Filter {
  
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, 
                        FilterChain chain) throws IOException, ServletException {
      
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        HttpServletResponse httpResponse = (HttpServletResponse) response;
      
        // 获取请求源
        String origin = httpRequest.getHeader("Origin");
      
        // 检查是否为允许的源
        if (isAllowedOrigin(origin)) {
            httpResponse.setHeader("Access-Control-Allow-Origin", origin);
        }
      
        // 设置其他CORS头
        httpResponse.setHeader("Access-Control-Allow-Credentials", "true");
        httpResponse.setHeader("Access-Control-Allow-Methods", 
                             "GET, POST, PUT, DELETE, OPTIONS");
        httpResponse.setHeader("Access-Control-Allow-Headers", 
                             "Content-Type, Authorization, X-Requested-With");
        httpResponse.setHeader("Access-Control-Max-Age", "3600");
      
        // 处理预检请求
        if ("OPTIONS".equalsIgnoreCase(httpRequest.getMethod())) {
            httpResponse.setStatus(HttpServletResponse.SC_OK);
            return;
        }
      
        chain.doFilter(request, response);
    }
  
    private boolean isAllowedOrigin(String origin) {
        if (origin == null) {
            return false;
        }
      
        // 允许的源列表
        Set<String> allowedOrigins = new HashSet<>();
        allowedOrigins.add("http://localhost:3000");
        allowedOrigins.add("https://app.example.com");
        allowedOrigins.add("https://www.example.com");
      
        return allowedOrigins.contains(origin);
    }
  
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        // 初始化逻辑
    }
  
    @Override
    public void destroy() {
        // 清理逻辑
    }
}

3. 中间件方式处理

Spring Security CORS配置
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
  
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .cors().and() // 启用CORS
            .csrf().disable()
            .authorizeRequests()
                .antMatchers("/public/**").permitAll()
                .anyRequest().authenticated()
            .and()
            .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS);
    }
  
    @Bean
    public CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration configuration = new CorsConfiguration();
      
        // 配置允许的源
        configuration.setAllowedOriginPatterns(Arrays.asList("*"));
      
        // 配置允许的方法
        configuration.setAllowedMethods(Arrays.asList("*"));
      
        // 配置允许的头
        configuration.setAllowedHeaders(Arrays.asList("*"));
      
        // 配置是否允许凭证
        configuration.setAllowCredentials(true);
      
        // 配置预检请求缓存时间
        configuration.setMaxAge(3600L);
      
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);
      
        return source;
    }
}

Java中的处理方案

1. Spring Boot CORS处理

配置类方式
@Configuration
public class CORSConfiguration {
  
    @Bean
    public WebMvcConfigurer corsConfigurer() {
        return new WebMvcConfigurer() {
            @Override
            public void addCorsMappings(CorsRegistry registry) {
                registry.addMapping("/**")
                    .allowedOriginPatterns("*")
                    .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
                    .allowedHeaders("*")
                    .allowCredentials(true)
                    .maxAge(3600);
            }
        };
    }
}
控制器级别配置
@RestController
@RequestMapping("/api")
public class ApiController {
  
    @CrossOrigin(origins = "http://localhost:3000")
    @GetMapping("/data")
    public ResponseEntity<Map<String, Object>> getData() {
        Map<String, Object> data = new HashMap<>();
        data.put("message", "Hello from API");
        data.put("timestamp", System.currentTimeMillis());
      
        return ResponseEntity.ok(data);
    }
  
    @CrossOrigin(
        origins = {"http://localhost:3000", "https://app.example.com"},
        methods = {RequestMethod.POST, RequestMethod.PUT},
        allowedHeaders = {"Content-Type", "Authorization"}
    )
    @PostMapping("/submit")
    public ResponseEntity<String> submitData(@RequestBody String data) {
        return ResponseEntity.ok("Data submitted successfully");
    }
}

2. 自定义CORS处理器

自定义CORS处理器
@Component
public class CustomCORSHandler implements CorsProcessor {
  
    @Override
    public boolean processRequest(@Nullable CorsConfiguration config, 
                                HttpServletRequest request, 
                                HttpServletResponse response) throws IOException {
      
        // 获取请求源
        String origin = request.getHeader("Origin");
      
        // 如果没有配置CORS,直接返回
        if (config == null) {
            return true;
        }
      
        // 处理预检请求
        if (isPreFlightRequest(request)) {
            return handlePreFlightRequest(config, request, response);
        }
      
        // 处理实际请求
        return handleActualRequest(config, request, response);
    }
  
    private boolean isPreFlightRequest(HttpServletRequest request) {
        return "OPTIONS".equals(request.getMethod()) &&
               request.getHeader("Access-Control-Request-Method") != null;
    }
  
    private boolean handlePreFlightRequest(CorsConfiguration config, 
                                        HttpServletRequest request, 
                                        HttpServletResponse response) {
      
        // 设置预检响应头
        response.setHeader("Access-Control-Allow-Origin", 
                          config.getAllowedOrigins().iterator().next());
        response.setHeader("Access-Control-Allow-Methods", 
                          String.join(",", config.getAllowedMethods()));
        response.setHeader("Access-Control-Allow-Headers", 
                          String.join(",", config.getAllowedHeaders()));
        response.setHeader("Access-Control-Max-Age", 
                          String.valueOf(config.getMaxAge()));
      
        if (Boolean.TRUE.equals(config.getAllowCredentials())) {
            response.setHeader("Access-Control-Allow-Credentials", "true");
        }
      
        response.setStatus(HttpServletResponse.SC_OK);
        return false; // 不继续处理
    }
  
    private boolean handleActualRequest(CorsConfiguration config, 
                                      HttpServletRequest request, 
                                      HttpServletResponse response) {
      
        // 设置实际请求的CORS头
        String origin = request.getHeader("Origin");
        if (config.getAllowedOrigins().contains(origin)) {
            response.setHeader("Access-Control-Allow-Origin", origin);
        }
      
        if (Boolean.TRUE.equals(config.getAllowCredentials())) {
            response.setHeader("Access-Control-Allow-Credentials", "true");
        }
      
        return true; // 继续处理
    }
}

3. 动态CORS配置

动态CORS配置服务
@Service
public class DynamicCORSService {
  
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
  
    public CorsConfiguration getDynamicCORSConfig(String origin) {
        CorsConfiguration config = new CorsConfiguration();
      
        // 从数据库或缓存获取动态配置
        CORSConfig dbConfig = getCORSConfigFromDB(origin);
      
        if (dbConfig != null) {
            config.setAllowedOrigins(Arrays.asList(dbConfig.getAllowedOrigins()));
            config.setAllowedMethods(Arrays.asList(dbConfig.getAllowedMethods()));
            config.setAllowedHeaders(Arrays.asList(dbConfig.getAllowedHeaders()));
            config.setAllowCredentials(dbConfig.isAllowCredentials());
            config.setMaxAge(dbConfig.getMaxAge());
        } else {
            // 使用默认配置
            config.setAllowedOriginPatterns(Arrays.asList("*"));
            config.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE"));
            config.setAllowedHeaders(Arrays.asList("*"));
            config.setAllowCredentials(false);
            config.setMaxAge(3600L);
        }
      
        return config;
    }
  
    private CORSConfig getCORSConfigFromDB(String origin) {
        // 从数据库获取CORS配置
        String key = "cors:config:" + origin;
        return (CORSConfig) redisTemplate.opsForValue().get(key);
    }
  
    public void updateCORSConfig(String origin, CORSConfig config) {
        // 更新CORS配置
        String key = "cors:config:" + origin;
        redisTemplate.opsForValue().set(key, config, Duration.ofHours(24));
      
        // 清除相关缓存
        clearCORSConfigCache(origin);
    }
  
    private void clearCORSConfigCache(String origin) {
        // 清除CORS配置缓存
        String cacheKey = "cors:cache:" + origin;
        redisTemplate.delete(cacheKey);
    }
}

4. 微服务CORS处理

网关CORS配置
@Configuration
public class GatewayCORSConfig {
  
    @Bean
    public CorsWebFilter corsWebFilter() {
        CorsConfiguration config = new CorsConfiguration();
      
        // 允许所有源(生产环境应该限制)
        config.addAllowedOriginPattern("*");
      
        // 允许所有方法
        config.addAllowedMethod("*");
      
        // 允许所有头
        config.addAllowedHeader("*");
      
        // 允许凭证
        config.setAllowCredentials(true);
      
        // 预检请求缓存时间
        config.setMaxAge(3600L);
      
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", config);
      
        return new CorsWebFilter(source);
    }
}
微服务CORS配置
@Configuration
public class MicroserviceCORSConfig {
  
    @Bean
    public CorsFilter corsFilter() {
        CorsConfiguration config = new CorsConfiguration();
      
        // 只允许网关访问
        config.addAllowedOrigin("http://localhost:8080");
        config.addAllowedOrigin("https://gateway.example.com");
      
        // 允许的方法
        config.addAllowedMethod("GET");
        config.addAllowedMethod("POST");
        config.addAllowedMethod("PUT");
        config.addAllowedMethod("DELETE");
      
        // 允许的头
        config.addAllowedHeader("Content-Type");
        config.addAllowedHeader("Authorization");
        config.addAllowedHeader("X-Requested-With");
      
        // 不允许凭证(微服务间通信)
        config.setAllowCredentials(false);
      
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", config);
      
        return new CorsFilter(source);
    }
}

5. 错误处理和监控

CORS错误处理器
@ControllerAdvice
public class CORSErrorHandler {
  
    @ExceptionHandler(CORSException.class)
    public ResponseEntity<ErrorResponse> handleCORSError(CORSException e) {
        ErrorResponse error = ErrorResponse.builder()
            .error("CORS_ERROR")
            .message(e.getMessage())
            .timestamp(LocalDateTime.now())
            .build();
      
        return ResponseEntity.status(HttpStatus.FORBIDDEN).body(error);
    }
  
    @ExceptionHandler(InvalidOriginException.class)
    public ResponseEntity<ErrorResponse> handleInvalidOrigin(InvalidOriginException e) {
        ErrorResponse error = ErrorResponse.builder()
            .error("INVALID_ORIGIN")
            .message("请求源不被允许: " + e.getMessage())
            .timestamp(LocalDateTime.now())
            .build();
      
        return ResponseEntity.status(HttpStatus.FORBIDDEN).body(error);
    }
}
CORS监控服务
@Component
public class CORSMonitoringService {
  
    @Autowired
    private MeterRegistry meterRegistry;
  
    public void recordCORSRequest(String origin, String method, boolean allowed) {
        if (allowed) {
            meterRegistry.counter("cors.requests.allowed", 
                                "origin", origin, "method", method).increment();
        } else {
            meterRegistry.counter("cors.requests.blocked", 
                                "origin", origin, "method", method).increment();
        }
    }
  
    public void recordPreflightRequest(String origin, boolean success) {
        if (success) {
            meterRegistry.counter("cors.preflight.success", "origin", origin).increment();
        } else {
            meterRegistry.counter("cors.preflight.failed", "origin", origin).increment();
        }
    }
  
    @EventListener
    public void handleCORSRequestEvent(CORSRequestEvent event) {
        log.info("CORS请求事件: origin={}, method={}, allowed={}, timestamp={}", 
                event.getOrigin(), event.getMethod(), event.isAllowed(), event.getTimestamp());
    }
}

最佳实践

1. 安全最佳实践

安全建议:
  - 不要使用通配符*允许所有源
  - 明确指定允许的HTTP方法
  - 限制允许的请求头
  - 谨慎使用allowCredentials
  - 实现源验证逻辑
  - 监控异常CORS请求
  - 定期审查CORS配置

2. 性能最佳实践

性能建议:
  - 合理设置预检请求缓存时间
  - 使用Redis缓存CORS配置
  - 实现请求频率限制
  - 避免不必要的预检请求
  - 使用CDN减少跨域请求

3. 配置最佳实践

配置建议:
  - 开发环境允许本地源
  - 生产环境限制特定域名
  - 使用环境变量配置
  - 实现配置热更新
  - 记录CORS配置变更

4. 测试和验证

@SpringBootTest
class CORSConfigurationTest {
  
    @Autowired
    private TestRestTemplate restTemplate;
  
    @Test
    void testCORSHeaders() {
        // 测试CORS头是否正确设置
        ResponseEntity<String> response = restTemplate.getForEntity(
            "http://localhost:8080/api/test", String.class);
      
        HttpHeaders headers = response.getHeaders();
        assertThat(headers.get("Access-Control-Allow-Origin")).isNotNull();
        assertThat(headers.get("Access-Control-Allow-Methods")).isNotNull();
    }
  
    @Test
    void testPreflightRequest() {
        // 测试预检请求
        HttpHeaders headers = new HttpHeaders();
        headers.set("Origin", "http://localhost:3000");
        headers.set("Access-Control-Request-Method", "POST");
        headers.set("Access-Control-Request-Headers", "Content-Type");
      
        HttpEntity<Void> request = new HttpEntity<>(headers);
        ResponseEntity<String> response = restTemplate.exchange(
            "http://localhost:8080/api/test", 
            HttpMethod.OPTIONS, request, String.class);
      
        assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
    }
}

总结

Java跨域(CORS)处理是企业级应用开发中的重要技术,通过本文章的学习,您应该能够:

  1. 理解跨域的基本概念和同源策略
  2. 掌握不同类型跨域请求的处理方式
  3. 分析跨域系统的重难点和解决方案
  4. 在Spring Boot中正确配置和处理CORS
  5. 实现安全、高效的跨域解决方案
  6. 遵循最佳实践,构建可靠的跨域处理系统

跨域处理需要综合考虑安全性、性能、兼容性等多个方面,建议在实际项目中根据具体需求选择合适的处理方案,并定期审查和优化配置。

本文章已经生成可运行项目
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值