环境:SpringBoot2.7.16
1. 简介
跨源资源共享(CORS,或通俗地译为跨域资源共享)是一种基于 HTTP 头的机制,该机制通过允许服务器标示除了它自己以外的其他源(域、协议或端口),使得浏览器允许这些源访问加载自己的资源。跨源资源共享还通过一种机制来检查服务器是否会允许要发送的真实请求,该机制通过浏览器发起一个到服务器托管的跨源资源的“预检”请求。在预检中,浏览器发送的头中标示有 HTTP 方法和真实请求中会用到的头。
跨源 HTTP 请求例子:运行在https://www.a.com 的 JavaScript 代码使用 XMLHttpRequest 来发起一个到 https://www.b.com/data.json 的请求。
出于安全性,浏览器限制脚本内发起的跨源 HTTP 请求。例如,XMLHttpRequest 和 Fetch API 遵循同源策略。这意味着使用这些 API 的 Web 应用程序只能从加载应用程序的同一个域请求 HTTP 资源,除非响应报文包含了正确 CORS 响应头。
图片
什么情况下需要 CORS?
这份跨源共享标准允许在下列场景中使用跨站点 HTTP 请求:
- XMLHttpRequest 或 Fetch API 发起的跨源 HTTP 请求。
- Web 字体(CSS 中通过 @font-face 使用跨源字体资源),因此,网站就可以发布 TrueType 字体资源,并只允许已授权网站进行跨站调用。
- WebGL 贴图。
- 使用 drawImage() 将图片或视频画面绘制到 canvas。
- 来自图像的 CSS 图形 (en-US)。
跨源资源共享标准新增了一组 HTTP 标头字段,允许服务器声明哪些源站通过浏览器有权限访问哪些资源。另外,规范要求,对那些可能对服务器数据产生副作用的 HTTP 请求方法(特别是 GET 以外的 HTTP 请求,或者搭配某些 MIME 类型的 POST 请求),浏览器必须首先使用 OPTIONS 方法发起一个预检请求(preflight request),从而获知服务端是否允许该跨源请求。服务器确认允许之后,才发起实际的 HTTP 请求。在预检请求的返回中,服务器端也可以通知客户端,是否需要携带身份凭证(例如 Cookie 和 HTTP 认证相关数据)。
CORS 请求失败会产生错误,但是为了安全,在 JavaScript 代码层面无法获知到底具体是哪里出了问题。你只能查看浏览器的控制台以得知具体是哪里出现了错误。
什么是预检请求?
一个 CORS 预检请求是用于检查服务器是否支持 CORS 即跨域资源共享。它一般是用了以下几个 HTTP 请求首部的 OPTIONS 请求:Access-Control-Request-Method 和 Access-Control-Request-Headers,以及一个 Origin 首部。当有必要的时候,浏览器会自动发出一个预检请求;所以在正常情况下,前端开发者不需要自己去发这样的请求。
举个例子,一个客户端可能会在实际发送一个 DELETE 请求之前,先向服务器发起一个预检请求,用于询问是否可以向服务器发起一个 DELETE 请求:
OPTIONS /resource/foo
Access-Control-Request-Method: DELETE
Access-Control-Request-Headers: origin, x-requested-with
Origin: https://foo.bar.org
如果服务器允许,那么服务器就会响应这个预检请求。并且其响应头 Access-Control-Allow-Methods 会将 DELETE 包含在其中:
HTTP/1.1 200 OK
Content-Length: 0
Connection: keep-alive
Access-Control-Allow-Origin: https://foo.bar.org
Access-Control-Allow-Methods: POST, GET, OPTIONS, DELETE
Access-Control-Max-Age: 86400
针对CORS的HTTP Headers:
- Access-Control-Allow-Origin指示响应的资源是否可以被给定的来源共享。
- Access-Control-Allow-Credentials指示当请求的凭证标记为 true 时,是否可以公开对该请求响应。
- Access-Control-Allow-Headers用在对预检请求的响应中,指示实际的请求中可以使用哪些 HTTP 标头。
- Access-Control-Allow-Methods指定对预检请求的响应中,哪些 HTTP 方法允许访问请求的资源。
- Access-Control-Expose-Headers通过列出标头的名称,指示哪些标头可以作为响应的一部分公开。
- Access-Control-Max-Age指示预检请求的结果能被缓存多久。
- Access-Control-Request-Headers用于发起一个预检请求,告知服务器正式请求会使用哪些 HTTP 标头。
- Access-Control-Request-Method用于发起一个预检请求,告知服务器正式请求会使用哪一种 HTTP 请求方法。
- Origin指示获取资源的请求是从什么源发起的。
- Timing-Allow-Origin指定特定的源,以允许其访问 Resource Timing API 功能提供的属性值,否则由于跨源限制,这些值将被报告为零。
大致了解了CORS后,接下来介绍在SpringBoot中如何解决跨域问题
2. 实战案例
Spring MVC HandlerMapping实现提供了对CORS的内置支持。在成功地将请求映射到处理程序之后,HandlerMapping实现检查给定请求和处理程序的CORS配置,并采取进一步的操作。Preflight requests(预检请求)被直接处理,而简单和实际的CORS请求被拦截、验证,并设置了所需的CORS响应头。
2.1 @CrossOrigin
@CrossOrigin注释允许对带注释的控制器方法进行跨域请求,如下例所示:
@RestController
@RequestMapping("/accounts")
public class AccountController {
@CrossOrigin
@GetMapping("/{id}")
public Account retrieve(@PathVariable Long id) {
// ...
}
@DeleteMapping("/{id}")
public void remove(@PathVariable Long id) {
// ...
}
}
默认情况下,@CrossOrigin允许:
- 所有请求来源origins。
- 所有请求headers。
- 控制器方法映射到的所有HTTP方法。
注意:默认情况下不启用allowCredentials,因为它建立了一个公开敏感的用户特定信息(如Cookie和CSRF令牌)的信任级别,并且只应在适当的地方使用。启用时,必须将allowOrigins设置为一个或多个特定域(但不是特殊值“*”),或者可以使用allowOringPatterns属性来匹配一组动态原点。
maxAge 默认30分钟。
@CrossOrigin在类级别也受支持,并由所有方法继承,如下例所示:
@CrossOrigin(origins = "https://www.pack.com", maxAge = 3600)
@RestController
@RequestMapping("/accounts")
public class AccountController {
@GetMapping("/{id}")
public Account retrieve(@PathVariable Long id) {
// ...
}
}
你可以在类级别和方法级别使用@CrossOrigin,如下例所示:
@CrossOrigin(maxAge = 3600)
@RestController
@RequestMapping("/accounts")
public class AccountController {
@CrossOrigin("http://www.pack.com")
@GetMapping("/{id}")
public Account retrieve(@PathVariable Long id) {
// ...
}
}
2.2 全局配置
除了细粒度的控制器方法级配置外,你还可以全局CORS配置。可以在任何HandlerMapping上单独设置基于URL的CorsConfiguration映射。
默认情况下,全局配置启用以下功能:
- 所有请求来源origins.
- 所有请求headers.
- GET, HEAD, and POST methods.
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOrigins("http://www.pack.com")
.allowedMethods("PUT", "DELETE")
.allowedHeaders("header1", "header2", "header3")
.exposedHeaders("header1", "header2")
.allowCredentials(true).maxAge(3600) ;
}
}
2.3 CORS过滤器
可以通过内置的CorsFilter应用CORS支持。
注意:如果你尝试将CorsFilter与Spring Security一起使用,请记住Spring Security内置了对CORS的支持。
要配置筛选器,请将CorsConfigurationSource传递给其构造函数,如下例所示:
@Bean
public CorsFilter corsFilter() {
CorsConfiguration config = new CorsConfiguration() ;
config.setAllowCredentials(true) ;
config.addAllowedOrigin("http://www.pack.com") ;
config.addAllowedHeader("*") ;
config.addAllowedMethod("*") ;
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource() ;
source.registerCorsConfiguration("/**", config) ;
CorsFilter filter = new CorsFilter(source) ;
return filter ;
}
当然你也可以使用自定义的Filter来解决CORS问题。
@WebFilter("/*")
public class WebCORSFilter implements Filter {
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletResponse response = (HttpServletResponse) res ;
HttpServletRequest request = (HttpServletRequest) req ;
String origin = request.getHeader("Origin") ;
request.setCharacterEncoding("UTF-8") ;
response.setHeader("Access-Control-Allow-Origin", origin) ;
response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE, PUT") ;
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Credentials", "true") ;
response.setHeader("Access-Control-Allow-Headers",
"Origin, X-Requested-With, Content-Type, Accept, Account, access-token") ;
chain.doFilter(req, res) ;
}
}
以上是本篇文章的全部内容,希望对你有帮助。