Spring CORS Guide:
What It Is and
How to Enable It

stackhawk

StackHawk|June 18, 2021

Find out what cross-origin resource sharing (CORS) is, why it's important, and how to properly work with it in Spring.

As explained in the CSRF post, cross-origin resource sharing (CORS) is a safety mechanism that prevents scripts from executing malicious code in websites and lets scripts do cross-domain calls. As I'll explain in more detail in this post, a cross-domain call is an HTTP request done via the browser from domain A to domain B via AJAX. An "origin" in the context of a cross-domain call is the combination of the request's protocol, port, and domain. Originally proposed in the early 2000s, it's now a standard across all modern browsers. In this post, I'll explain what CORS is, why it's important, and how to properly work with it in Spring.

Why Use CORS?

Before CORS

Let's start by describing the situation before CORS was implemented. Before CORS, a request from domain A to domain B via an AJAX call was blocked—permanently. Let's understand why.  Assume that you've entered your bank's website at https://somebank.com to transfer funds to an account. However, you didn't enter the URL directly. You clicked on a link in an email. Unfortunately, this was a phishing email, and you were redirected to the address https://notabank.com. You're presented with a form to send money, as described in the CSRF post

The transfer funds request looks like this:

POST http://somebank.com/transfer.funds HTTP/1.1 
account=validAccount&amount=1000

The form on the website that constructs this request looks like this:

<input type="hidden" name="account" value="200"/> <input type="hidden" name="amount" value="1000000"/> <input type="submit" value="transfer funds"/> </form> 

And the attacker sends the request via this code:

<script>
function send() {
    var sendForm = new XMLHttpRequest();
    sendForm.open("POST","http://somebank.com/transfer.funds",true);
    sendForm.setRequestHeader("Content-Type", "application/json");
    sendForm.send(JSON.stringify({"account":"200", "amount":1000000})); 
}
</script>

As I've explained in the CSRF post, the money isn't sent to the bank account you want it to be sent to (let's say you typed in bank account id:100). Instead, the money goes to the attacker's bank account (id:200) via manipulating the form parameters. The attacker takes the data and sends it via AJAX to https://somebank.com. The money is now in the attacker's bank account. That's why we prevent cross-origin requests.

Spring CORS Guide - Picture 1 image

Adding CORS

However, there are a lot of scenarios for which you have legitimate reasons to do a cross-domain request. The standard specifies that a request shares the server's origin only if the two have the same scheme, domain, and port number (if the URL includes a port). For instance, https://a.com and http://a.com are not the same origin. Likewise, https://a.com and https://a.com:8080 are not the same origin.

Examples

However, it is a perfectly valid use case to have your website hosted on https://a.com and your API to be hosted on https://api.a.com. Without a mechanism to fetch data from the API, your website can't access it. Likewise, if you use a third-party API—let's say from https://api.salesforce.com—you won't be able to use it from your SaaS JavaScript. Lastly, a company can have two applications that are hosted on the same URL with different ports, and they need to communicate with each other. In other words, there are plenty of legitimate uses cases for which a mechanism was needed to provide more flexibility and ease of use.

How Does CORS Work?

CORS basically requires each request done from domain A to domain B to be explicitly permitted by domain's B server. In other words, an AJAX request from domain A to domain B is to be blocked by default. Unless we take active measures on domain B's side. To enable requests coming from domain A, we need to set the access control policy on domain B. We can select which domains may make requests to domain B via AJAX and the allowed methods (POST, PUT, GET, etc.). Or we can just provide a wildcard permission: "*" (which usually isn't recommended).

Examples: Two Types of Requests

Simple Requests

The standard specifies two scenarios for performing a cross-origin AJAX request—a "simple" request, and a request that requires a "preflight request" first. As described in MDN, a “simple request” is one that meets all the following conditions

  • One of the allowed methods:

    • GET
    • HEAD
    • POST
  • The only headers we can use:

    • Accept
    • Accept-Language
    • Content-Language
    • Content-Type (but note the additional requirements below)

  • For the Content-Type header, the only allowed values are:

    • application/x-www-form-urlencoded
    • multipart/form-data
    • text/plain

TL;DR: If you do a GET/HEAD/POST request and don't set the content types, other than the content types above (let's say application/json), you're making a "simple request." 

When the server responds to such a request, it'll contain a header called "Access-Control-Allow-Origin" that can contain a list of allowed domains or "*" to denote that any domain can do a request to this server. We control the domains, methods, and headers that can do a CORS request on the server side.

Requests That Require a Preflight Request First

Basically, this type of request includes any request that doesn't fit the definition above. The browser will automatically send an OPTIONS method to the server to make sure that the original request can be handled. We do this as these kinds of requests usually alter data on the server, and thus we want to protect the data integrity. The response for the options request lists the domains, HTTP methods, and HTTP headers for which we allow a CORS request.

Spring Framework

A popular Java framework for developing enterprise and web applications is the Spring Framework. Pivotal created the Spring Framework back in 2005 and it's still in use today. Over the years, Pivotal added different modules to the core Spring container. One of the most popular is Spring Boot, which is the Convention over Configuration (CoC) part of the Spring framework. It allows you to code with minimal configuration. 

Spring Security

Another popular module is Spring Security. As the official documentation explains very well, "Spring Security is a powerful and highly customizable authentication and access-control framework. It is the de-facto standard for securing Spring-based applications. Spring Security is a framework that focuses on providing both authentication and authorization to Java applications. Like all Spring projects, the real power of Spring Security is found in how easily it can be extended to meet custom requirements." In other words, this is the standard security module for Spring-based applications. It provides protection against attacks like session fixation, clickjacking, and cross-site request forgery.

The real power of Spring Security is found in how easily it can be extended to meet custom requirements.

CORS in Spring Boot and Spring Security

CORS in Spring Boot

We can enable CORS for a method, a whole class, or globally in Spring boot. To add it to a method, we just add it to a specific handler in spring-mvc annotated with @RequestMapping annotation:

Method-Level CORS

@RestController 
@RequestMapping("/sample") 
public class SampleController { 
@CrossOrigin(origins = "https://a.com")
@RequestMapping(method = RequestMethod.POST, ) 
public Sample Test(@PathVariable Long id) { // ... }

As you can see, the Test method accepts a request from the domain https://a.com only.

Class-Level CORS

@CrossOrigin(origins = "https://a.com") 
@RestController 
@RequestMapping("/test") 
public class SampleController { 
@RequestMapping(method = RequestMethod.GET, path = "/{id}") 
public Test info(@PathVariable Long id) { // ... } 
@RequestMapping(method = RequestMethod.DELETE, path = "/{id}") 
public void undo(@PathVariable Long id) { // ... } }

In the example above, we add the annotation on the class level so both info and undo method conform with the annotation's settings.

Global CORS

We set the global configuration as a @Configuration annotation for the MVC framework:

@Configuration 
@EnableWebMvc 
public class CorsConfig implements WebMvcConfigurer { 
@Override 
public void addCorsMappings(CorsRegistry registry) { 
registry.addMapping("/**");
registery.allowedOrigins("https://a.com"); 
} 
}

The mapping above applies to all routes and enables requests from one origin. 

CORS in Spring Security

If we use Spring Security, we need to add one extra step. Otherwise, Spring Security will reject the request before reaching the MVC framework. This is because Spring Security automatically protects every endpoint with a requirement of an authentication token. This is irrelevant in the case of preflight requests as the purpose of an OPTIONS request, as described above, is only to establish whether the original request can go through. To make Spring Security bypass preflight requests, add this configuration:

@EnableWebSecurity 
public class WebSecurityConfig extends WebSecurityConfigurerAdapter { 
@Override protected
protected void configure(HttpSecurity http) throws Exception { h
http.cors(); 
}}

Conclusion

Add Automated Security Testing to Your Pipeline for Free

CORS is a useful mechanism for enabling more flexible layouts and more responsive applications. To use it, we need to actively enable it on Spring Boot or Spring Security. In addition, we want to minimize the risk that comes with this kind of request. Hence, we need to make sure that we enable it only for the endpoints, domains, and methods where it's necessary. 

This post was written by Alexander Fridman. Alexander is a veteran in the software industry with over 11 years of experience. He worked his way up the corporate ladder and has held the positions of Senior Software Developer, Team Leader, Software Architect, and CTO. Alexander is experienced in frontend development and DevOps, but he specializes in backend development.


StackHawk  |  June 18, 2021