MVC 跨域配置
在拦截器部分中有提到过实现跨域配置,就是通过重写 WebMvcConfigurer 中的 addCorsMappings 方法实现,除此以外还有两种办法实现跨域配置
@CrossCors
注解CorsFilter
方法
@CrossCors
注解
该注解通过在服务端配置 CORS 规则,解决浏览器的跨域拦截问题。它会自动添加必要的响应头,允许指定域、方法或头部的跨域请求,应用在控制器/方法
常用属性:
origins
属性,设置允许的请求来源。[]
数组,可以填写多个请求来源value
属性,和origins
属性相同,是它的别名allowCredentials
属性,是否允许客户端请求发送 CookiemaxAge
属性,本次预检请求的有效期,单位为秒
@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;
}
}
非简单请求
非简单请求是那种对服务器有特殊要求的请求,比如请求方法是PUT
或DELETE
,或者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
内执行,与业务逻辑相关的拦截(如权限、日志)