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)