Spring MVC allows you to handle CORS (cross-origin resource sharing).
For security reasons, browsers prohibit AJAX calls to resources outside of the current origin. For example, you might have your bank account on one tab, and evil.com on another. Scripts from evil.com should not be able to make AJAX requests to your bank's API with your credentials - for example, withdraw money from your account!
Cross-origin resource sharing (CORS) is W3C specification implemented in most browsers, which allows you to determine which cross-domain requests are allowed, rather than using less secure and less powerful workarounds based on IFRAME or JSONP.
Processing
The CORS specification distinguishes between preflight, simple, and actual requests. To understand how CORS works, you can read this article. among other things, or refer to the specification for details.
The HandlerMapping
implementations from
Spring WebFlux provide native CORS support. After successfully mapping a request to a handler,
HandlerMapping
checks the CORS configuration for that request and handler and takes further action.
Preliminary requests are processed directly, while simple and actual CORS requests are intercepted, validated, and
the required CORS response headers are set.
To allow requests between different origins (i.e., the Origin
is present and different from the request host), you need to provide some explicitly declared CORS
configuration. If no suitable CORS configuration is found, preflight requests are rejected. Responses to simple and
actual CORS requests do not have CORS headers added and are therefore rejected by browsers.
Each HandlerMapping
can be configured individually using CorsConfiguration
mappings based on URL
patterns. In
most cases, applications use WebFlux Java configuration to declare these mappings, resulting in a single global Map
passed to all HandlerMapping
implementations.
You can combine global CORS configuration at the
HandlerMapping
with more fine-grained CORS tuning at the handler level. For example, annotated
controllers may use @CrossOrigin
annotations at the class or method level (other handlers may implement
CorsConfigurationSource
).
The rules for combining global and local configuration are typically
additive - for example, all global and all local sources. For attributes where only one value can be accepted, such
as allowCredentials
and maxAge
, the local value overrides the global one. For more
information, see the section on CorsConfiguration#combine(CorsConfiguration)
.
To learn more from the original source or to perform advanced configuration, see:
CorsConfiguration
CorsProcessor
andDefaultCorsProcessor
AbstractHandlerMapping
@CrossOrigin
Annotation @CrossOrigin
allows queries between different sources to annotated controller
methods, as shown in the following example:
@RestController
@RequestMapping("/account")
public class AccountController {
@CrossOrigin
@GetMapping("/{id}")
public Mono<Account> retrieve(@PathVariable Long id) {
// ...
}
@DeleteMapping("/{id}")
public Mono<Void> remove(@PathVariable Long id) {
// ...
}
}
@RestController
@RequestMapping("/account")
class AccountController {
@CrossOrigin
@GetMapping("/{id}")
suspend fun retrieve(@PathVariable id: Long): Account {
// ...
}
@DeleteMapping("/{id}")
suspend fun remove(@PathVariable id: Long) {
// ...
}
}
By default, @CrossOrigin
allows:
All sources.
All headers.
All HTTP methods that the method is associated with controller.
allowCredentials
is not enabled by default because this sets the level of trust at which sensitive
user information (such as cookies and CSRF tokens), so it should only be used in cases where it is really necessary.
If enabled, then either allowOrigins
must be set for one or more specific domains (but not the special
value "*"
), or alternatively the allowOriginPatterns property can be used to mappings to a dynamic set
of sources.
maxAge
is set to 30 minutes.
@CrossOrigin
is supported both at
the class level and is inherited by all methods. The following example specifies a specific domain and sets maxAge
to an hour:
@CrossOrigin(origins = "https://domain2.com", maxAge = 3600)
@RestController
@RequestMapping("/account")
public class AccountController {
@GetMapping("/{id}")
public Mono<Account> retrieve(@PathVariable Long id) {
// ...
}
@DeleteMapping("/{id}")
public Mono<Void> remove(@PathVariable Long id) {
// ...
}
}
@CrossOrigin("https://domain2.com", maxAge = 3600)
@RestController
@RequestMapping("/account")
class AccountController {
@GetMapping("/{id}")
suspend fun retrieve(@PathVariable id: Long): Account {
// ...
}
@DeleteMapping("/{id}")
suspend fun remove(@PathVariable id: Long) {
// ...
}
}
You can use the @CrossOrigin
annotation at both the class and method level, as shown in the
following example:
@CrossOrigin(maxAge = 3600)
@RestController
@RequestMapping("/account")
public class AccountController {
@CrossOrigin("https://domain2.com")
@GetMapping("/{id}")
public Mono<Account> retrieve(@PathVariable Long id) {
// ...
}
@DeleteMapping("/{id}")
public Mono<Void> remove(@PathVariable Long id) {
// ...
}
}
- Using an annotation
@CrossOrigin
at the class level. - Using the
@CrossOrigin
annotation at the method level.
@CrossOrigin(maxAge = 3600)
@RestController
@RequestMapping("/account")
class AccountController {
@CrossOrigin("https://domain2.com")
@GetMapping("/{id}")
suspend fun retrieve(@PathVariable id: Long): Account {
// ...
}
@DeleteMapping("/{id}")
suspend fun remove(@PathVariable id: Long) {
// ...
}
}
- Using the
@CrossOrigin
annotation at the class level. - Using the
@CrossOrigin
annotation at the method level.
Global configuration
In addition to fine-tuning at the controller method level, you'll likely need
to define global CORS configuration as well. You can configure URL-based CorsConfiguration
mappings
individually for any HandlerMapping
. However, most applications use the WebFlux Java configuration for
this.
By default, the global configuration includes the following:
All sources.
All headers.
Methods
GET
,HEAD
andPOST
.
allowCredentials
is not enabled by default because this sets the level of trust at which sensitive
user information (such as cookies and CSRF) is disclosed tokens), so it should only be used in cases where it is
truly needed. If enabled, then either allowOrigins
should be set for one or more specific domains (but
not the special value "*"
), or alternatively you can use the allowOriginPatterns property to match
against a dynamic set of origins.
maxAge
is set to 30 minutes.
To enable CORS in WebFlux
Java configuration, you can use the CorsRegistry
callback, as shown in the following example:
@Configuration
@EnableWebFlux
public class WebConfig implements WebFluxConfigurer {
@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 displays...
}
}
@Configuration
@EnableWebFlux
class WebConfig : WebFluxConfigurer {
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 displays...
}
}
WebFilter
for CORS
You can apply CORS support via the built-in CorsWebFilter
which is great for functional endpoints.
CorsFilter
with Spring Security, be aware that
Spring Security provides built-in support CORS.
To configure a filter, you can declare a CorsWebFilter
bean and pass CorsConfigurationSource
to its constructor, as shown in the following example:
@Bean
CorsWebFilter corsFilter() {
CorsConfiguration config = new CorsConfiguration();
// Perhaps...
// config.applyPermitDefaultValues()
config.setAllowCredentials(true);
config.addAllowedOrigin("https://domain1.com");
config.addAllowedHeader("*");
config.addAllowedMethod("*");
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
return new CorsWebFilter(source);
}
@Bean
fun corsFilter(): CorsWebFilter {
val config = CorsConfiguration()
// Perhaps...
// config.applyPermitDefaultValues()
config.allowCredentials = true
config.addAllowedOrigin("https://domain1.com")
config.addAllowedHeader("*")
config.addAllowedMethod("*")
val source = UrlBasedCorsConfigurationSource().apply {
registerCorsConfiguration("/**", config)
}
return CorsWebFilter(source)
}