跨域资源共享 (CORS)

一、什么是跨域?

跨域是指浏览器发起的请求,其目标服务器与当前页面的来源(域名、协议、端口)不一致。例如:

  • 同源请求: 页面和接口来源一致,比如:
    • 页面地址:http://example.com
    • 请求接口:http://example.com/api
  • 跨域请求: 页面和接口来源不同,比如:
    • 页面地址:http://localhost:3000
    • 请求接口:http://localhost:8080/api

跨域问题的根源在于 浏览器的同源策略(Same-Origin Policy),这是一种安全机制,防止恶意站点通过 JavaScript 等方式访问用户敏感数据。

同源策略限制:浏览器的同源策略只允许当前网页(前端)的域名、端口、和协议与后端一致。如果前后端运行在不同的端口、协议或域名下(如前端在 http://localhost:3000,后端在 http://localhost:8080),这就是跨域,浏览器会阻止请求。

跨域的目的:

  • 如果跨域配置仅允许特定来源(如前端站点 http://localhost:3000)访问,那就是为了让你的前端能调用后端。
  • 如果配置允许所有来源(*),则是为了对任何来源的请求都开放,可能包括来自其他网站或恶意来源。

二、跨域问题的表现及解决方案

表现: 当浏览器发起跨域请求时,如果目标服务器没有正确配置跨域资源共享(CORS),会报类似以下错误:

Access to XMLHttpRequest at 'http://example.com/api' from origin 'http://localhost:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

解决方案:
目标服务器通过 CORS 协议,明确告诉浏览器允许跨域请求。CORS 协议依赖以下 HTTP 头部字段:

  • Access-Control-Allow-Origin:允许的请求来源(如 http://localhost:3000*)。
  • Access-Control-Allow-Methods:允许的请求方法(如 GET, POST)。
  • Access-Control-Allow-Headers:允许的请求头(如 Content-Type, Authorization)。
  • Access-Control-Allow-Credentials:是否允许发送 Cookies 和凭据。

三、Spring Boot 中的跨域处理

Spring Boot 提供了多种方式来配置 CORS,根据项目需求灵活选择。

1. 局部跨域配置(@CrossOrigin )

使用 @CrossOrigin 注解,在控制器或具体方法上配置跨域规则,适合简单场景。

示例代码:

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

@RestController
public class MyController {

    @CrossOrigin(origins = "http://localhost:3000") // 允许来自 http://localhost:3000 的请求
    @GetMapping("/api/data")
    public String getData() {
        return "Hello, CORS!";
    }
}

优缺点:

  • 优点: 配置简单,针对单个接口有效。
  • 缺点: 对于多个接口的跨域需求不方便管理。

2. 全局跨域配置(针对Controller层)

方式 1:CorsRegistry(针对Spring MVC)

通过实现 WebMvcConfigurer 接口,配置全局跨域规则,适用于基于 Spring MVC 的项目。

示例代码:

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 WebConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")  // 匹配所有接口
                .allowedOrigins("http://localhost:3000") // 允许的请求来源
                .allowedMethods("GET", "POST", "PUT", "DELETE") // 允许的请求方法
                .allowedHeaders("*") // 允许的请求头
                .allowCredentials(true) // 允许携带 Cookies 或凭据
                .maxAge(3600); // OPTIONS 预检请求的缓存时间
    }
}

根据CORS规范,当 allowCredentials(true) 时,allowedOrigins 必须指定具体的源,不能是通配符 "*"

  • allowCredentials 设置为 true 时,意味着请求可以携带认证信息(如 Cookies)
  • 如果同时允许所有源(*),会存在严重的安全漏洞
  • 攻击者可以从任何域发送包含凭证的跨域请求

如果不遵守这个规定,就无法允许任何的跨域请求并报错403。(博主血的教训)

方式 2:CorsWebFilter(针对Spring WebFlux)

对于基于 Spring WebFlux 的响应式项目,需要使用 CorsWebFilter 处理跨域。

示例代码:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.reactive.CorsWebFilter;
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;

@Configuration
public class CorsConfig {

    @Bean
    public CorsWebFilter corsWebFilter() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        CorsConfiguration config = new CorsConfiguration();
        config.addAllowedOrigin("*"); // 允许所有来源
        config.addAllowedHeader("*"); // 允许所有请求头
        config.addAllowedMethod("*"); // 允许所有方法
        config.setAllowCredentials(true); // 允许凭据
        source.registerCorsConfiguration("/**", config);
        return new CorsWebFilter(source);
    }
}

两种方式的区别

  • CorsRegistry

    • 属于 Spring MVC,用于配置基于 Servlet 的传统 Web 应用。
    • 实现 WebMvcConfigurer 接口,通过 addCorsMappings 方法设置 CORS 规则。
    • 常用于传统的 Spring Boot Web 应用。
  • CorsWebFilter

    • 属于 Spring WebFlux,用于响应式(Reactive)编程模型的项目。
    • 基于过滤器机制实现,配置较灵活。
    • 适合需要处理响应式流的项目(如高并发或非阻塞请求)。

3. Spring Security 跨域配置

如果不通过上面说到的针对Controller层的全局跨域配置,也可以通过在Spring Security的SecurityFilterChain中使用 http.cors()来支持跨域,与之前的Controller层的全局跨域配置二者取其一就可以。

代码示例:

@Configuration
public class SecurityConfig {

    @Autowired
    private JwtAuthenticationFilter jwtAuthenticationFilter;

   @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
             .cors(cors -> cors.configurationSource(request -> {
                    CorsConfiguration configuration = new CorsConfiguration();
                    configuration.setAllowedOriginPatterns(List.of("*")); // 允许所有源
                    configuration.setAllowedMethods(List.of("*")); // 允许所有方法
                    configuration.setAllowedHeaders(List.of("*")); // 允许所有请求头
                    configuration.setAllowCredentials(true); // 注意:使用 * 时必须设置为 false
                    return configuration;
                }));

        return http.build();
    }
  • Spring Security中配置跨域的时候一般不用AllowedOrigin,而使用AllowedOriginPatterns,这样就可以规避之前提到的CORS规范(AllowedOriginPatterns设为"*"的同时AllowCredentials可以设为true)
  • 并且setAllowedOriginPatterns 方法允许使用更灵活的匹配模式,可以在URL中使用通配符,从而匹配多个源。例如:"http://*.example.com"

4.当同时配置了全局跨域配置与 Spring Security 跨域配置

4.1 Spring的过滤器链顺序

Spring应用中的请求处理遵循以下的过滤器链顺序:

  1. Servlet容器过滤器(如 CorsFilter)。
  2. Spring Security过滤器(如认证、授权过滤器)。
  3. Spring MVC拦截器(如 HandlerInterceptor)。
4.2 CORS过滤器的执行顺序
  • Spring Security的CORS配置:如果配置了 HttpSecurity.cors(),Spring Security将自动注册一个 CorsFilter,该过滤器位于Spring Security的过滤器链中。这意味着它会在Spring Security的认证和授权过滤器之后执行。

  • WebMvcConfigurer 的CORS配置:Spring MVC内部也有一个 CorsFilter,它位于Spring MVC的处理链中,通常在Spring Security过滤器链之后执行。

4.3 两种配置的交互

当同时使用 WebMvcConfigurer 和 Spring Security 的 HttpSecurity.cors() 来配置CORS时:

  • Spring Security的CORS配置优先于 WebMvcConfigurer 的CORS配置:因为Spring Security的过滤器位于过滤器链的前端,先于Spring MVC的过滤器执行。因此,Spring Security的CORS配置会首先生效。

  • 潜在的配置冲突:如果两者的CORS配置不一致,可能导致预期之外的行为。通常,建议选择其中一种方式进行CORS配置,以避免混淆和冲突。

四、Spring Cloud Gateway 的跨域处理

在微服务架构中,Spring Cloud Gateway 通常作为 API 网关,代理不同微服务的请求。跨域请求可以在网关层统一处理,避免每个微服务都重复配置跨域。

方式 1:使用 CorsWebFilter

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.reactive.CorsWebFilter;
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;

@Configuration
public class GatewayCorsConfig {
    @Bean
    public CorsWebFilter corsWebFilter() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        CorsConfiguration config = new CorsConfiguration();
        
        // 不能使用 "*",需要指定具体域名
        config.addAllowedOrigin("http://localhost:5173");
        config.addAllowedMethod("*");
        config.addAllowedHeader("*");
        config.setAllowCredentials(true);
        
        source.registerCorsConfiguration("/**", config);
        return new CorsWebFilter(source);
    }
}

方式 2:通过 application.yml 配置

spring:
  cloud:
    gateway:
      globalcors:
        cors-configurations:
          '[/**]':
            allowedOrigins: "http://localhost:3000"
            allowedMethods: "*"
            allowedHeaders: "*"
            allowCredentials: true

区别:

  • Spring Cloud Gateway 的 CorsWebFilter 作用于网关层,处理所有通过网关的跨域请求。
  • 普通 WebFlux 项目中的 CorsWebFilter 仅处理单个服务的跨域。

根据CORS规范,当 allowCredentials(true) 时,allowedOrigins 必须指定具体的源,不能是通配符 "*"

  • allowCredentials 设置为 true 时,意味着请求可以携带认证信息(如 Cookies)
  • 如果同时允许所有源(*),会存在严重的安全漏洞
  • 攻击者可以从任何域发送包含凭证的跨域请求

如果不遵守这个规定,就无法允许任何的跨域请求并报错403。(博主血的教训)

### 资源共享 (CORS) 的配置教程 #### 1. 基本概念 资源共享(Cross-Origin Resource Sharing, CORS)是一种基于 HTTP 头部的安全机制,用于控制浏览器与其他之间的资源交互。当一个网页尝试通过 AJAX 请求访问另一个不同源的资源时,浏览器会检查目标服务器是否允许该请求。 为了支持请求,服务器需要在响应中添加特定的头部信息来声明哪些来源被授权访问其资源[^1]。 --- #### 2. 主要的 CORS 响应头及其作用 以下是实现 CORS 所需的关键响应头: - **Access-Control-Allow-Origin**: 指定允许访问资源的来源。例如 `http://api.bob.com` 表示仅允许此名下的请求;也可以设置为通配符 `*` 来允许任意来源,但这通常只适用于不需要携带身份验证信息的情况[^1]。 - **Access-Control-Allow-Credentials**: 如果设置了这个字段并赋值为 `true`,则表明服务器允许发送带有凭证(如 Cookie 或 Authorization Header)的请求。需要注意的是,在这种情况下,`Access-Control-Allow-Origin` 不再能使用通配符[^3]。 - **Access-Control-Allow-Methods**: 列举出客户端可以使用的 HTTP 方法,比如 GET、POST 和 PUT 等[^2]。 - **Access-Control-Allow-Headers**: 定义了预检请求中所列明的额外自定义头部名称列表,这些头部将在实际请求里被接受[^2]。 - **Access-Control-Max-Age**: 缓存时间长度(秒数),在此期间无需再次发起 OPTIONS 预检请求[^4]。 - **Access-Control-Expose-Headers**: 明确指出那些非标准头部能够暴露给前端脚本读取[^1]。 --- #### 3. 实现步骤 ##### 后端服务端配置 假设我们正在构建 RESTful API 并希望它支持调用,则可以在 Web 应用框架内部处理 CORS 设置。以下是一些常见编程语言/框架的例子: ###### Node.js Express 示例 ```javascript const express = require('express'); const app = express(); // 使用 cors 中间件简化操作 const cors = require('cors'); app.use(cors({ origin: 'http://example.com', // 替换为目标网站 URL credentials: true // 支持带认证信息的请求 })); // 自定义方式手动设置响应头 app.all('*', function(req, res, next){ res.header("Access-Control-Allow-Origin", "http://example.com"); res.header("Access-Control-Allow-Headers", "Content-Type,Authorization"); res.header("Access-Control-Allow-Methods","GET,PUT,POST,DELETE,PATCH,OPTIONS"); if ('OPTIONS' === req.method) { res.sendStatus(200); } else { next(); } }); app.listen(3000, () => console.log('Server running on port 3000')); ``` ###### Python Flask 示例 ```python from flask import Flask, jsonify from flask_cors import CORS app = Flask(__name__) CORS(app, resources={r"/api/*": {"origins": "http://example.com"}}) @app.route('/api/data') def get_data(): return jsonify({"message": "This is cross-origin data!"}) if __name__ == '__main__': app.run(debug=True) ``` --- ##### 浏览器端调整 对于某些场景下可能还需要修改前端代码配合完成整个流程。特别是涉及到敏感数据传输的时候,记得开启 withCredentials 属性以便于传递 Cookies 或其他形式的身份令牌[^3]: ```javascript var xhr = new XMLHttpRequest(); xhr.open('GET', 'https://target-domain/api/resource', true); // 开启凭据共享功能 xhr.withCredentials = true; xhr.onreadystatechange = function() { if (this.readyState === this.DONE && this.status === 200) { console.log(this.responseText); } }; xhr.send(null); ``` --- #### 4. 注意事项 - 当涉及复杂请求(即除了简单方法外的所有情况)之前都会先触发一次 OPTION 类型的预飞行探测请求,此时务必要确保服务器正确回应相应的权限声明。 - 对于生产环境而言,推荐显式列举可信站点而非采用全局开放策略(`*`)以增强安全性。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值