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 and DefaultCorsProcessor

  • AbstractHandlerMapping

@CrossOrigin

Annotation @CrossOrigin allows queries between different sources to annotated controller methods, as shown in the following example:

Java

@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) {
// ...
}
}
Kotlin

@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:

Java

@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) {
// ...
}
}
Kotlin

@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:

Java

@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) {
// ...
}
}
  1. Using an annotation @CrossOrigin at the class level.
  2. Using the @CrossOrigin annotation at the method level.
Kotlin

@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) {
// ...
}
}
  1. Using the @CrossOrigin annotation at the class level.
  2. 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 and POST.

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:

Java

@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...
}
}
Kotlin

@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.

If you try to use 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:

Java

@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);
}
Kotlin

@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)
}