MVC入门(4)-- 跨域配置

发布于:2025-05-21 ⋅ 阅读:(17) ⋅ 点赞:(0)

MVC 跨域配置

在拦截器部分中有提到过实现跨域配置,就是通过重写 WebMvcConfigurer 中的 addCorsMappings 方法实现,除此以外还有两种办法实现跨域配置

  • @CrossCors 注解
  • CorsFilter 方法

@CrossCors 注解

该注解通过在服务端配置 CORS 规则,解决浏览器的跨域拦截问题。它会自动添加必要的响应头,允许指定域、方法或头部的跨域请求,应用在控制器/方法

常用属性:

  • origins 属性,设置允许的请求来源。[] 数组,可以填写多个请求来源
  • value 属性,和 origins 属性相同,是它的别名
  • allowCredentials 属性,是否允许客户端请求发送 Cookie
  • maxAge 属性,本次预检请求的有效期,单位为秒
@RestController
@RequestMapping("/demo")
public class DemoController {

    @GetMapping("/echo")
    // @CrossOrigin(origins = "*", allowCredentials = "true", maxAge = 3600)
    @CrossOrigin(originPatterns = "*", allowCredentials = "true", maxAge = 3600)
    public String demo() {
        return "Hello World!";
    }

}

需要注意,当使用 allowCredentials = true 时,可能会出现 When allowCredentials is true, allowedOrigins cannot contain the special value "*" since that cannot be set on the "Access-Control-Allow-Origin" response header. To allow credentials to a set of origins, list them explicitly or consider using "allowedOriginPatterns" instead. 错误,根据错误提示,需要将 origins 属性 改成 originPatterns 属性

CorsFilter 方法

@Configuration
@RequiredArgsConstructor
public class SpringMVCConfiguration implements WebMvcConfigurer {
    @Bean
    public FilterRegistrationBean<CorsFilter> corsFilter() {
        // 配置源,类似 CorsRegistry 注册表
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        // 配置,相当于 CorsRegistration 注册信息
        CorsConfiguration config = new CorsConfiguration();
        // 允许发送 Cookie
        config.setAllowCredentials(true);
        // 允许所有请求来源
        config.addAllowedOriginPattern("*");
        // 允许所有请求 Header
        config.addAllowedHeader("*");
        // 允许所有请求 Method
        config.addAllowedMethod("*");
        // 有效期
        config.setMaxAge(1600L);
        source.registerCorsConfiguration("/**", config);
        // 创建 CorsFilter 过滤器
        FilterRegistrationBean<CorsFilter> bean = new FilterRegistrationBean<>(new CorsFilter(source));
        // // 设置 order 排序
        bean.setOrder(0);
        
        return bean;
    }
}

非简单请求

非简单请求是那种对服务器有特殊要求的请求,比如请求方法是PUTDELETE,或者Content-Type字段的类型是application/json

非简单请求的 CORS 请求,会在正式通信之前,增加一次 HTTP 查询请求,称为"预检"请求(preflight)。

浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些 HTTP 动词和头信息字段。只有得到肯定答复,浏览器才会发出正式的XMLHttpRequest请求,否则就报错。

"预检"请求用的请求方法是OPTIONS,表示这个请求是用来询问的。头信息里面,关键字段是Origin,表示请求来自哪个源。除此之外还包括两个特殊字段

  • Access-Control-Request-Method:该字段是必须的,用来列出浏览器的 CORS 请求会用到哪些 HTTP 方法
  • Access-Control-Request-Headers:该字段是一个逗号分隔的字符串,指定浏览器 CORS 请求会额外发送的头信息字段
OPTIONS /cors HTTP/1.1
Origin: http://api.bob.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: X-Custom-Header
Host: api.alice.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...

确认允许跨源请求

HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:15:39 GMT
Server: Apache/2.0.61 (Unix)
Access-Control-Allow-Origin: http://api.bob.com
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: X-Custom-Header
Content-Type: text/html; charset=utf-8
Content-Encoding: gzip
Content-Length: 0
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Content-Type: text/plain

关键的是Access-Control-Allow-Origin字段,表示http://api.bob.com可以请求数据。该字段也可以设为星号,表示同意任意跨源请求

如果服务器否定了"预检"请求,会返回一个正常的 HTTP 回应,但是没有任何 CORS 相关的头信息字段

XMLHttpRequest cannot load http://api.alice.com.
Origin http://api.bob.com is not allowed by Access-Control-Allow-Origin.

一旦服务器通过了"预检"请求,以后每次浏览器正常的 CORS 请求,就都跟简单请求一样,会有一个Origin头信息字段。服务器的回应,也都会有一个Access-Control-Allow-Origin头信息字段。

跨域配置中的坑

在发起非简单请求时,会自动先先发起 OPTIONS “预检”请求,要求服务器确认是否能够这样请求。这样,这个请求就会被 SpringMVC 的拦截器所处理。这是因为 Handler 和拦截器的执行顺序,且预检请求不会包含Cookie信息,因此被权限拦截器提前结束。

请求处理流程

  • Servlet 过滤器:首先经过所有注册的 Filter,如果手动配置了 CorsFilter,会在此阶段处理跨域请求
    • 可用于请求日志记录、权限校验、请求修改等
    • 在请求进入 Spring MVC 之前执行
  • DispatcherServlet:请求进入 Spring MVC 核心的 DispatcherServlet
    • 概念:作为 Spring MVC 的前端控制器(Front Controller),它是所有请求的入口和协调中心,负责请求的分发和响应处理
    • 在 Servlet 过滤器之后接管请求,协调整个请求处理流程,通过 HandlerMapping 找到控制器方法,调用 HandlerAdapter 执行控制器方法,处理视图解析和响应生成
  • HandlerMapping:DispatcherServlet 通过 HandlerMapping 找到匹配的控制器(Controller)或处理器方法(Handler Method),此时会检查 CORS 配置,如 @CrossOrigin 或全局 CORS 配置
    • 作用:根据请求的 URL、HTTP 方法等条件,找到对应的控制器方法(Handler Method)
    • DispatcherServlet 内调用,是请求处理流程的第一个关键步骤
  • 预检请求处理:如果是 OPTIONS 预检请求(如复杂跨域请求),Spring 会直接在此阶段生成响应(添加 CORS 头),跳过后续的拦截器和业务逻辑
  • Interceptor 拦截器:执行拦截器的 preHandle() 方法,如果拦截器返回 false,请求被终止
  • Handler 执行:调用具体的控制器方法(Handler Method)处理请求
  • Interceptor 拦截器:执行拦截器的 postHandle()afterCompletion() 方法

Handler 和 拦截器的执行顺序

根据请求流程可知 DispatchServlet.doDispatch() 方法是 SpringMVC 的核心入口方法,分析发现所有的拦截器的 preHandle() 方法的执行都在实际 Handler 的方法(比如某个 API 对应的业务方法)之前,其中任意拦截器返回false都会跳过后续所有处理过程。

而 SpringMVC 对预检请求的处理则在 PreFlightHandler.handleRequest() 中处理,在整个处理链条中出于后置位。由于预检请求中不带 Cookie,因此先被权限拦截器拦截。

@CrossCors 是一种更细粒度的配置方法。SpringMVC 把其处理细节包装在 getHandler(processedRequest); 中,也是滞后于权限拦截器的。

因此推荐采用 CorsFilter 过滤器,避免 OPTIONS 预检查走到拦截器里

拦截器与其他组件的关联

  • Filter 组件:Servlet 层面,在 DispatcherServlet 之前执行,全局请求处理(如编码、安全)
  • Interceptor 组件:Spring MVC 层面,在 DispatcherServlet 内执行,与业务逻辑相关的拦截(如权限、日志)

参考


网站公告

今日签到

点亮在社区的每一天
去签到