CORS
Spring MVC 允許您處理 CORS(跨域資源共享)。本節介紹如何進行處理。
簡介
出於安全原因,瀏覽器禁止 AJAX 呼叫訪問當前來源之外的資源。例如,您可能在一個標籤頁中開啟銀行賬戶,在另一個標籤頁中開啟 evil.com。來自 evil.com 的指令碼不應該能夠使用您的憑據向您的銀行 API 發起 AJAX 請求——例如從您的賬戶中取款!
憑據請求
將 CORS 與憑據請求一起使用需要啟用 allowedCredentials
。請注意,此選項會與配置的域建立高度信任,並且透過暴露敏感的使用者特定資訊(如 cookie 和 CSRF 令牌)來增加 Web 應用程式的攻擊面。
啟用憑據還會影響配置的“*
”CORS 萬用字元的處理方式
-
allowOrigins
中不允許使用萬用字元,但可以改用allowOriginPatterns
屬性來匹配動態來源集合。 -
在
allowedHeaders
或allowedMethods
上設定時,透過複製 CORS 預檢請求中指定的相關的標頭和方法來處理Access-Control-Allow-Headers
和Access-Control-Allow-Methods
響應標頭。 -
在
exposedHeaders
上設定時,Access-Control-Expose-Headers
響應標頭會設定為配置的標頭列表或萬用字元。雖然 CORS 規範不允許在Access-Control-Allow-Credentials
設定為true
時使用萬用字元,但大多數瀏覽器都支援它,並且在 CORS 處理期間並非所有響應標頭都可用,因此,無論allowCredentials
屬性的值如何,指定萬用字元時都使用萬用字元作為標頭值。
雖然此類萬用字元配置很方便,但在可能的情況下,建議配置一組有限的值以提供更高的安全性。 |
處理
CORS 規範區分了預檢請求(preflight)、簡單請求(simple)和實際請求(actual)。要了解 CORS 的工作原理,您可以閱讀 本文(還有許多其他文章),或檢視規範瞭解更多詳細資訊。
Spring MVC 的 HandlerMapping
實現提供了對 CORS 的內建支援。成功將請求對映到處理程式後,HandlerMapping
實現會檢查給定請求和處理程式的 CORS 配置並採取進一步操作。預檢請求會直接處理,而簡單請求和實際 CORS 請求會被攔截、驗證,並設定所需的 CORS 響應標頭。
為了啟用跨域請求(即,存在 Origin
標頭且與請求的主機不同),您需要進行一些顯式宣告的 CORS 配置。如果未找到匹配的 CORS 配置,則預檢請求會被拒絕。不會向簡單請求和實際 CORS 請求的響應中新增 CORS 標頭,因此瀏覽器會拒絕它們。
每個 HandlerMapping
都可以透過基於 URL 模式的 CorsConfiguration
對映進行單獨配置。在大多數情況下,應用程式使用 MVC Java 配置或 XML namespace 來宣告此類對映,這會導致將單個全域性對映傳遞給所有 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 namespace 來實現此目的。
預設情況下,全域性配置啟用以下內容:
-
所有來源。
-
所有標頭。
-
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 namespace 中啟用 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)