Spring MVC处理 CORS 跨域
跨源资源共享(CORS) 即 Cross-Origin Resource Sharing,也常被译为跨域资源共享。作为 W3C 的标准,它允许浏览器向跨源服务器发起请求,克服了 AJAX 只能同源使用的限制。
CORS 需要浏览器和服务器同时支持,浏览器发起跨域请求时会自动携带一些请求头,服务器如果允许跨域,也会自动添加一些响应头。作为运行在服务端的 Spring MVC 也对 CORS 提供了支持,并提供了多种解决方案。
浏览器同源策略
同源策略是 Netscape 公司在 1995 年引入浏览器的,目前所有浏览器都遵循了同源策略。
同源表示两个网页的 协议、域名、端口号 三者都一致。同源策略最初的目的是为了保护 A 网页设置的 cookie 在 B 网页中不能读取。
设想如果用户同时打开了银行网站和钓鱼网站,如果没有同源策略,钓鱼网站拿到银行网站的 cookie 后发起转账或者读取用户敏感信息,将会很危险。
非同源的网页,对 Cookie、LocalStorage、IndexDB 读取、Dom 文档获取、Ajax 请求有着严格的限制。同源策略规定了只能向同源的网址发起 AJAX 请求。CORS 正是解决跨域 AJAX 的标准方案,相比使用 JSONP 方案来说更为灵活。
CORS 处理流程
浏览器的请求可以分为简单请求和非简单请求两种。浏览器对不同类型的请求进行 CORS 处理的方式有所不同。
简单请求
满足下面三个条件的请求可以被称为简单请求:
- 请求方法为 GET、HEAD、POST 之一。
- 允许设置的请求头包括 Accept、Accept-Language、Content-Language、Content-Type。
- Content-Type 的值仅限于 text/plain、multipart/form-data、application/x-www-form-urlencoded。
简单请求流程如下图所示:
浏览器发起请求时在请求头添加 Origin 字段,表示当前资源所在的源,服务端收到请求后检查该字段,如果允许该请求则在响应头添加Access-Control-Allow-Origin 字段,否则可以拒绝处理请求并返回错误的 HTTP 响应码,浏览器收到响应后发现没有 Access-Control-Allow-Origin 字段或者 Access-Control-Allow-Origin 字段值有误则会在控制台打印不允许跨域的错误信息。
非简单请求
对于非简单请求,浏览器首先会发起预请求 Preflight Request 检查是否允许跨域,如果允许跨域才会执行真正的请求,流程如下所示:
预请求的 HTTP 方法为 OPTIONS,携带的请求头如下:
- Origin:表示资源所在的源。
- Access-Control-Request-Method:表示真实 HTTP 请求方法的请求头。
- Access-Control-Request-Headers:表示真实 HTTP 请求方法自定义的请求头 。
如果服务端允许跨域请求,会在响应头添加如下的字段:
- Access-Control-Allow-Origin :表示跨域请求允许的源,* 表示允许任何源。
- Access-Control-Request-Method:表示跨域请求允许使用的请求方法,可以比请求头中的多。
- Access-Control-Request-Headers:表示跨域请求允许携带的请求头。
如果服务端不允许跨域请求,则可以直接返回表示错误的 HTTP 响应码。
浏览器收到预请求响应后检查响应头判断是否允许跨域,如果不允许跨域则直接在控制台打印跨域报错信息。如果允许跨域再正常发起请求,携带请求头 Origin、Access-Control-Request-Method、Access-Control-Request-Headers 以及自定义的请求头。
携带用户身份的跨域请求
服务端检查跨域请求后,除了返回基本的响应头,还可以添加如下额外的响应头:
- Access-Control-Max-Age:表示跨域检查结果在浏览器中可以缓存的秒数。
- Access-Control-Expose-Headers:默认情况 JS 只能获取一些基本的响应头,这个字段允许 JS 可以获取除基本响应头的其他响应头。
除此之外,服务端还可以返回值为 true 的响应头 Access-Control-Allow-Credentials,这个响应头可以让浏览器在跨域请求时携带 Cookie 信息,当然了,需要在发起请求时配置 withCredentials=true。
如果服务端返回了响应头 Access-Control-Allow-Credentials,此时 Access-Control-Allow-Origin 不能返回 *,否则请求将会失败。
Spring MVC CORS 处理
由于每个接口都需要处理跨域请求,因此在传统的 Java Web 项目中通常使用 Filter 进行全局处理。
Spring MVC 中进行跨域处理的核心类是 HandlerMapping,当请求到达 DispatchServlet,如果请求是预请求 Spring 会将处理器替换为跨域处理器,如果请求是非预请求 Spring 将在拦截器链前面添加跨域拦截器,然后根据 CORS 配置进行相应的处理。再把 DispatcherServlet 流程图祭出,和 CORS 相关的部分可以见右上角:
CorsFilter
Filter 是解决跨域的传统方式,Spring 出现前,我们经常会写一个解决跨域的 Filter,当请求到来时向响应头中添加固定的字段。
Spring MVC 提供了一个具有相同功能的 CorsFilter,这样以后我们就不需要每个项目都单独写一个处理跨域的 Filter 了。
SpringBoot 环境下配置 CorsFilter 示例如下:
@Configuration
public class WebMvcConfig {
@Bean
public Filter corsFilter() {
CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.addAllowedOrigin("http://hukp.cn");
corsConfiguration.addAllowedMethod(HttpMethod.POST);
corsConfiguration.addAllowedHeader("token");
corsConfiguration.setExposedHeaders(Arrays.asList("header1", "header2"));
corsConfiguration.setMaxAge(3600L);
corsConfiguration.setAllowCredentials(true);
UrlBasedCorsConfigurationSource corsConfigurationSource = new UrlBasedCorsConfigurationSource();
corsConfigurationSource.registerCorsConfiguration("/*", corsConfiguration);
CorsFilter corsFilter = new CorsFilter(corsConfigurationSource);
return corsFilter;
}
}
实例化 CorsFilter 时需要指定一个 CorsConfigurationSource 实例用来获取跨域配置 CorsConfiguration,常用的实现是 UrlBasedCorsConfigurationSource。
全局 CORS 配置
Spring MVC 官方解决 CORS 的做法是在 HandlerMappping 获取处理器链时根据是否为预请求使用 PreFlightHandler 作为处理器或者添加拦截器 CorsInterceptor,具体可以参见源码AbstractHandlerMapping#getCorsHandlerExecutionChain。
对于用户而言,只需要进行 CORS 配置就可以了,而配置分为全局配置和局部配置,Spring 会把这两个配置进行合并。对于全局配置而言有 API 和 XML 两种配置方式。
XML 配置
XML 配置是 Spring 早期提供的支持,和上述 CorsFilter 等价的 CORS 配置如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd">
<mvc:cors>
<mvc:mapping path="/*"
allowed-origins="http://hukp.com"
allowed-methods="POST"
allowed-headers="token"
exposed-headers="header1, header2"
max-age="3600"
allow-credentials="true"
/>
</mvc:cors>
</beans>
API 配置
当前注解已经成为 Spring 的主流使用方式,使用 @EnableWebMvc 开启 Web 相关特性后可以通过实现接口 WebMvcConfiger 进行跨域配置,最终这个配置将传递到 AbstractHandlerMapping。和 XML 等价的 API 配置方式如下:
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/*")
.allowedOrigins("http://hukp.cn")
.allowedMethods("POST")
.allowedHeaders("token")
.exposedHeaders("header1", "header2")
.maxAge(3600)
.allowCredentials(true);
}
}
局部 CORS 配置
除了全局配置,Spring 还可以针对每个处理器做特殊的配置。
API 配置
如果想用一个处理器类处理一个请求,这个处理器类可以实现接口 HttpRequestHandler、Controller 或者 HandlerFunction,如果想要为这个处理器进行 CORS 处理,还需要实现接口 CorsConfigurationSource。以登录场景为例,示例代码如下:
public class LoginHandler implements Controller, CorsConfigurationSource {
@Override
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
// 省略相关逻辑
return new ModelAndView();
}
// 获取 CORS 配置
@Override
public CorsConfiguration getCorsConfiguration(HttpServletRequest request) {
CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.addAllowedOrigin("http://hukp.cn");
corsConfiguration.addAllowedMethod(HttpMethod.POST);
corsConfiguration.addAllowedHeader("token");
corsConfiguration.setExposedHeaders(Arrays.asList("header1", "header2"));
corsConfiguration.setMaxAge(3600L);
corsConfiguration.setAllowCredentials(true);
return corsConfiguration;
}
}
注解配置
Spring 添加对注解的支持后,我们使用 @Controller 标注控制器类,然后在类中使用 @RequestMapping 标注处理器方法。针对这种方式,由于请求由方法进行处理,我们没办法实现 CorsConfigurationSource 做跨域配置,但是 Spring 也提供了对应的解决方案。
我们可以使用 @CrossOrigin 注解做跨域配置,可以把这个类加在控制器类或者控制器方法上,控制器类上的 @CrossOrigin 适用于所有的控制器方法,控制器方法上的 @CrossOrigin 适用于自身,如果类和方法上都有 @CrossOrigin 注解,Spring 则会将配置合并。
示例代码如下:
@Controller
@CrossOrigin(origins = "http://hukp.cn",
allowedHeaders = "token")
public class LoginController {
@CrossOrigin(methods = RequestMethod.POST,
exposedHeaders = {"header1", "header2"},
maxAge = 3600L,
allowCredentials = "true")
@PostMapping("/login")
public ModelAndView login(HttpServletRequest request) {
// 省略业务逻辑
return new ModelAndView();
}
}
当请求 /login 到达时,将使用类和方法上合并后的 CORS 配置。
Spring Security CORS 处理
Spring Boot 环境下如果引入了 Spring Security,Spring 将自动配置 CorsFilter,此时从CorsConfigurationSource 类型的 bean 中读取 CORS 配置,因此将 CorsConfigurationSource 配置为 bean 即可。示例代码如下:
@Configuration
public class WebMvcConfig {
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.addAllowedOrigin("http://hukp.cn");
corsConfiguration.addAllowedMethod(HttpMethod.POST);
corsConfiguration.addAllowedHeader("token");
corsConfiguration.setExposedHeaders(Arrays.asList("header1", "header2"));
corsConfiguration.setMaxAge(3600L);
corsConfiguration.setAllowCredentials(true);
UrlBasedCorsConfigurationSource corsConfigurationSource = new UrlBasedCorsConfigurationSource();
corsConfigurationSource.registerCorsConfiguration("/*", corsConfiguration);
return corsConfigurationSource;
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律