Spring Web Reactive | 1. Spring WebFlux | 1.7. CORS

Web MVC

Spring WebFlux lets you handle CORS (Cross-Origin Resource Sharing). This section explains how.

1.7.1. Introduction

Web MVC

For security reasons, browsers prohibit AJAX calls to resources outside the current origin. For example, you could have your bank account open in one tab and evil.com in another tab. Scripts from evil.com cannot use your credentials to make AJAX requests to the bank API, such as withdrawing money from the account.

Cross-Origin Resource Sharing (CORS) is a W3C standard implemented by most browsers. It lets you specify allowed types of cross-domain requests instead of using less secure and less powerful workarounds based on IFRAME or JSONP.

1.7.2. Processing

Web MVC

The CORS specification distinguishes between preflight, simple, and actual requests. To learn how CORS works, see this article, among many others, or consult the specification for details.

Spring WebFlux HandlerMapping implementations provide built-in CORS support. After a request is normally mapped to a handler, HandlerMapping checks the CORS configuration for the given request and handler and performs the required action. Preflight requests are handled directly, while simple and actual CORS requests are intercepted, validated, and the necessary CORS response headers are set.

To enable cross-origin requests, that is, when an Origin header exists and differs from the request host, explicitly declared CORS configuration is required. If no matching CORS configuration is found, preflight requests are rejected. CORS headers are not added to simple and actual CORS request responses, so browsers reject them.

Each HandlerMapping can be configured separately with URL-pattern-based CorsConfiguration mappings. In most cases, applications declare these mappings through WebFlux Java configuration. This passes one global map to all HandlerMapping implementations.

You can combine global CORS configuration at the HandlerMapping level with more fine-grained handler-level CORS configuration. For example, annotated controller classes or methods can use the @CrossOrigin annotation, and other handlers can implement CorsConfigurationSource.

The rules for combining global and local settings are generally additive. For example, all global origins and all local origins are used. For properties that accept only a single value, such as allowCredentials and maxAge, the local value overrides the global value. For details, see CorsConfiguration#combine(CorsConfiguration).

To check details in the source or make advanced customizations, see the following.

  • CorsConfiguration
  • CorsProcessor, DefaultCorsProcessor
  • AbstractHandlerMapping

1.7.3. @CrossOrigin

Web MVC

The @CrossOrigin annotation enables cross-origin requests on annotated controller methods, as 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 the following:

  • All origins.
  • All headers.
  • All HTTP methods to which the controller method is mapped.

allowCredentials is not enabled by default. This is because it sets a trust level that exposes user-specific sensitive information such as cookies and CSRF tokens, so it should be used only when appropriate. If it is enabled, allowOrigins must be set to one or more specific domains, but not to the special value "*". Alternatively, the allowOriginPatterns property can be used to match a dynamic set of origins.

maxAge is set to 30 minutes.

@CrossOrigin is inherited by all supported methods at the class level. The following example specifies a particular domain and sets maxAge to one 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) {
        // ...
    }
}

As in the following example, @CrossOrigin can be used at both the class level and method level.

Java

@CrossOrigin(maxAge = 3600)   // (1)
@RestController
@RequestMapping("/account")
public class AccountController {

    @CrossOrigin("https://domain2.com")   // (2)
    @GetMapping("/{id}")
    public Mono<Account> retrieve(@PathVariable Long id) {
        // ...
    }

    @DeleteMapping("/{id}")
    public Mono<Void> remove(@PathVariable Long id) {
        // ...
    }
}

Kotlin

@CrossOrigin(maxAge = 3600)   // (1)
@RestController
@RequestMapping("/account")
class AccountController {

    @CrossOrigin("https://domain2.com")   // (2)
    @GetMapping("/{id}")
    suspend fun retrieve(@PathVariable id: Long): Account {
        // ...
    }

    @DeleteMapping("/{id}")
    suspend fun remove(@PathVariable id: Long) {
        // ...
    }
}
  • (1) Use @CrossOrigin at the class level.
  • (2) Use @CrossOrigin at the method level.

1.7.4. Global Configuration

Web MVC

In addition to fine-grained controller-method-level configuration, you probably also need to define global CORS configuration. URL-based CorsConfiguration mappings can be configured separately on HandlerMapping. However, most applications do this through WebFlux Java configuration.

By default, global configuration allows the following:

  • All origins.
  • All headers.
  • GET, HEAD, and POST methods.

allowedCredentials is not enabled by default. This is because it sets a trust level that exposes user-specific sensitive information such as cookies and CSRF tokens, so it should be used only when appropriate. If it is enabled, allowOrigins must be set to one or more specific domains, but not to the special value "*". Alternatively, the allowOriginPatterns property can be used to match 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 mappings...
    }
}

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 mappings...
    }
}

1.7.5. CORS WebFilter

Web MVC

You can apply CORS support by using the built-in CorsWebFilter. This is appropriate for functional endpoints.

If you intend to use CorsFilter with Spring Security, note that Spring Security includes CORS support.

To configure the filter, declare a CorsWebFilter Bean and pass CorsConfigurationSource to the constructor, as shown in the following example.

Java

@Bean
CorsWebFilter corsFilter() {

    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);

    return new CorsWebFilter(source);
}

Kotlin

@Bean
fun corsFilter(): CorsWebFilter {

    val config = CorsConfiguration()

    // Possibly...
    // config.applyPermitDefaultValues()

    config.allowCredentials = true
    config.addAllowedOrigin("https://domain1.com")
    config.addAllowedHeader("*")
    config.addAllowedMethod("*")

    val source = UrlBasedCorsConfigurationSource().apply {
        registerCorsConfiguration("/**", config)
    }
    return CorsWebFilter(source)
}