CORS
Spring MVC允许您处理CORS(跨源资源共享)。本节描述如何操作。
简介
出于安全原因,浏览器禁止对当前来源外部的资源进行AJAX调用。例如,您可能在一个选项卡中查看银行账户,而在另一个选项卡中查看evil.com。来自evil.com的脚本不应该能够使用您的凭据向您的银行API发出AJAX请求——例如,从您的账户中提取资金!
带凭据的请求
对带凭据的请求使用CORS需要启用allowedCredentials
。请注意,此选项与已配置的域建立了高级别的信任,并且还通过公开敏感的用户特定信息(例如cookie和CSRF令牌)来增加Web应用程序的攻击面。
启用凭据还会影响配置的"*"
CORS通配符的处理方式
-
通配符在
allowOrigins
中不被授权,但可以使用allowOriginPatterns
属性来匹配动态的来源集合。 -
当设置在
allowedHeaders
或allowedMethods
上时,Access-Control-Allow-Headers
和Access-Control-Allow-Methods
响应头通过复制CORS预检请求中指定的相关头和方法来处理。 -
当设置在
exposedHeaders
上时,Access-Control-Expose-Headers
响应头设置为配置的头列表或通配符字符。虽然CORS规范在Access-Control-Allow-Credentials
设置为true
时不允许使用通配符字符,但大多数浏览器都支持它,并且并非所有响应头在CORS处理期间都可用,因此,无论allowCredentials
属性的值如何,当指定通配符字符时,它都是使用的头值。
虽然这种通配符配置很方便,但建议尽可能配置有限的值集以提供更高的安全级别。 |
处理
CORS规范区分预检请求、简单请求和实际请求。要了解CORS的工作原理,您可以阅读这篇文章(以及许多其他文章),或查看规范以了解更多详细信息。
Spring MVC HandlerMapping
实现提供对CORS的内置支持。成功将请求映射到处理程序后,HandlerMapping
实现会检查给定请求和处理程序的CORS配置,并采取进一步的操作。预检请求直接处理,而简单和实际的CORS请求则被拦截、验证,并设置所需的CORS响应头。
为了启用跨域请求(即,存在Origin
头并且与请求的主机不同),您需要有一些显式声明的CORS配置。如果找不到匹配的CORS配置,则会拒绝预检请求。不会向简单和实际CORS请求的响应添加CORS头,因此浏览器会拒绝它们。
每个HandlerMapping
都可以配置其基于URL模式的CorsConfiguration
映射。在大多数情况下,应用程序使用MVC Java配置或XML命名空间来声明此类映射,这会导致将单个全局映射传递给所有HandlerMapping
实例。
您可以将HandlerMapping
级别的全局CORS配置与更细粒度的处理程序级CORS配置相结合。例如,带注解的控制器可以使用类级或方法级的@CrossOrigin
注解(其他处理程序可以实现CorsConfigurationSource
)。
组合全局和局部配置的规则通常是累加的——例如,所有全局和所有局部来源。对于那些只能接受单个值的属性,例如allowCredentials
和maxAge
,局部值会覆盖全局值。有关更多详细信息,请参阅CorsConfiguration#combine(CorsConfiguration)
。
要从源代码中了解更多信息或进行高级自定义,请检查背后的代码
|
@CrossOrigin
如以下示例所示,@CrossOrigin
注解可在带注解的控制器方法上启用跨域请求
-
Java
-
Kotlin
@RestController
@RequestMapping("/account")
public class AccountController {
@CrossOrigin
@GetMapping("/{id}")
public Account retrieve(@PathVariable Long id) {
// ...
}
@DeleteMapping("/{id}")
public void remove(@PathVariable Long id) {
// ...
}
}
@RestController
@RequestMapping("/account")
class AccountController {
@CrossOrigin
@GetMapping("/{id}")
fun retrieve(@PathVariable id: Long): Account {
// ...
}
@DeleteMapping("/{id}")
fun remove(@PathVariable id: Long) {
// ...
}
}
默认情况下,@CrossOrigin
允许:
-
所有来源。
-
所有标头。
-
控制器方法映射到的所有HTTP方法。
默认情况下不启用allowCredentials
,因为这会建立一个信任级别,该级别会公开敏感的用户特定信息(例如cookie和CSRF令牌),并且仅应在适当的情况下使用。如果启用它,则allowOrigins
必须设置为一个或多个特定域(但不能是特殊值"*"
),或者可以使用allowOriginPatterns
属性来匹配动态的来源集合。
maxAge
设置为30分钟。
@CrossOrigin
也支持在类级别使用,并被所有方法继承,如下例所示:
-
Java
-
Kotlin
@CrossOrigin(origins = "https://domain2.com", maxAge = 3600)
@RestController
@RequestMapping("/account")
public class AccountController {
@GetMapping("/{id}")
public Account retrieve(@PathVariable Long id) {
// ...
}
@DeleteMapping("/{id}")
public void remove(@PathVariable Long id) {
// ...
}
}
@CrossOrigin(origins = ["https://domain2.com"], maxAge = 3600)
@RestController
@RequestMapping("/account")
class AccountController {
@GetMapping("/{id}")
fun retrieve(@PathVariable id: Long): Account {
// ...
}
@DeleteMapping("/{id}")
fun remove(@PathVariable id: Long) {
// ...
}
您可以同时在类级别和方法级别使用@CrossOrigin
,如下例所示:
-
Java
-
Kotlin
@CrossOrigin(maxAge = 3600)
@RestController
@RequestMapping("/account")
public class AccountController {
@CrossOrigin("https://domain2.com")
@GetMapping("/{id}")
public Account retrieve(@PathVariable Long id) {
// ...
}
@DeleteMapping("/{id}")
public void remove(@PathVariable Long id) {
// ...
}
}
@CrossOrigin(maxAge = 3600)
@RestController
@RequestMapping("/account")
class AccountController {
@CrossOrigin("https://domain2.com")
@GetMapping("/{id}")
fun retrieve(@PathVariable id: Long): Account {
// ...
}
@DeleteMapping("/{id}")
fun remove(@PathVariable id: Long) {
// ...
}
}
全局配置
除了细粒度的控制器方法级别配置外,您可能还希望定义一些全局CORS配置。您可以分别在任何HandlerMapping
上设置基于URL的CorsConfiguration
映射。但是,大多数应用程序使用MVC Java配置或MVC XML命名空间来执行此操作。
默认情况下,全局配置启用以下内容:
-
所有来源。
-
所有标头。
-
GET
、HEAD
和POST
方法。
默认情况下不启用allowCredentials
,因为这会建立一个信任级别,该级别会公开敏感的用户特定信息(例如cookie和CSRF令牌),并且仅应在适当的情况下使用。如果启用它,则allowOrigins
必须设置为一个或多个特定域(但不能是特殊值"*"
),或者可以使用allowOriginPatterns
属性来匹配动态的来源集合。
maxAge
设置为30分钟。
Java配置
要在MVC Java配置中启用CORS,您可以使用CorsRegistry
回调,如下例所示:
-
Java
-
Kotlin
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOrigins("https://domain2.com")
.allowedMethods("PUT", "DELETE")
.allowedHeaders("header1", "header2", "header3")
.exposedHeaders("header1", "header2")
.allowCredentials(true).maxAge(3600);
// Add more mappings...
}
}
@Configuration
@EnableWebMvc
class WebConfig : WebMvcConfigurer {
override fun addCorsMappings(registry: CorsRegistry) {
registry.addMapping("/api/**")
.allowedOrigins("https://domain2.com")
.allowedMethods("PUT", "DELETE")
.allowedHeaders("header1", "header2", "header3")
.exposedHeaders("header1", "header2")
.allowCredentials(true).maxAge(3600)
// Add more mappings...
}
}
XML配置
要在XML命名空间中启用CORS,您可以使用<mvc:cors>
元素,如下例所示:
<mvc:cors>
<mvc:mapping path="/api/**"
allowed-origins="https://domain1.com, https://domain2.com"
allowed-methods="GET, PUT"
allowed-headers="header1, header2, header3"
exposed-headers="header1, header2" allow-credentials="true"
max-age="123" />
<mvc:mapping path="/resources/**"
allowed-origins="https://domain1.com" />
</mvc:cors>
CORS过滤器
您可以通过内置的CorsFilter
应用CORS支持。
如果您尝试将CorsFilter 与Spring Security一起使用,请记住Spring Security对CORS有内置支持。 |
要配置过滤器,请将其CorsConfigurationSource
传递给其构造函数,如下例所示:
-
Java
-
Kotlin
CorsConfiguration config = new CorsConfiguration();
// Possibly...
// config.applyPermitDefaultValues()
config.setAllowCredentials(true);
config.addAllowedOrigin("https://domain1.com");
config.addAllowedHeader("*");
config.addAllowedMethod("*");
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
CorsFilter filter = new CorsFilter(source);
val config = CorsConfiguration()
// Possibly...
// config.applyPermitDefaultValues()
config.allowCredentials = true
config.addAllowedOrigin("https://domain1.com")
config.addAllowedHeader("*")
config.addAllowedMethod("*")
val source = UrlBasedCorsConfigurationSource()
source.registerCorsConfiguration("/**", config)
val filter = CorsFilter(source)