CORS
Spring MVC 讓您可以處理 CORS (跨來源資源共用)。本節說明如何進行。
簡介
基於安全考量,瀏覽器禁止對目前來源以外的資源進行 AJAX 呼叫。例如,您可能在一個分頁中有您的銀行帳戶,而在另一個分頁中有 evil.com。來自 evil.com 的腳本不應能夠使用您的憑證對您的銀行 API 提出 AJAX 請求,例如從您的帳戶中提款!
具備憑證的請求
將 CORS 與具備憑證的請求搭配使用需要啟用 allowedCredentials
。請注意,此選項與組態的網域建立高度信任,並透過公開敏感的使用者特定資訊 (例如 Cookie 和 CSRF Token) 來增加 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 Token),且僅應在適當的情況下使用。當啟用時,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 Token),且僅應在適當的情況下使用。當啟用時,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)