Understanding CORS in Spring Boot

Cross-Origin Resource Sharing (CORS) is a security feature implemented by web browsers to restrict web pages from making requests to a domain different from the one that served the web page. In this article, we will explore what CORS is, why it's important, and how to configure it in a Spring Boot application. We'll provide both layman and technical explanations, along with real-world code examples and an in-depth look at its internal workings.

What is CORS?

  • Layman Explanation: Imagine you're at a restaurant, and you can only order food from that restaurant. You can't order food from another restaurant next door. This is a bit like how web browsers work: they only allow web pages to request data from the same domain they came from. CORS is a way to tell the browser that it's okay to get data from another domain.
  • Technical Explanation: CORS is a security feature implemented by web browsers to prevent web pages from making HTTP requests to a domain different from the one that served the web page. This policy is known as the Same-Origin Policy. CORS allows servers to specify who can access their resources and how by using specific HTTP headers.

Why is CORS Important?

CORS is important because it helps prevent security risks like cross-site request forgery (CSRF) and cross-site scripting (XSS) by ensuring that only trusted domains can access certain resources on your server. However, it also means that legitimate requests from different domains are blocked unless explicitly allowed.

Configuring CORS in Spring Boot

Spring Boot provides multiple ways to configure CORS. Let's explore some common methods.

1. Global CORS Configuration

You can configure CORS globally using a WebMvcConfigurer bean.

Example

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebConfig {

    @Bean
    public WebMvcConfigurer corsConfigurer() {
        return new WebMvcConfigurer() {
            @Override
            public void addCorsMappings(CorsRegistry registry) {
                registry.addMapping("/**")
                        .allowedOrigins("http://example.com")
                        .allowedMethods("GET", "POST", "PUT", "DELETE")
                        .allowedHeaders("*")
                        .allowCredentials(true)
                        .maxAge(3600);
            }
        };
    }
}

In this example, CORS is configured to allow requests from http://example.com for all endpoints (/**) and for specific HTTP methods.

2. Per-Controller CORS Configuration

You can also configure CORS on a per-controller basis using the @CrossOrigin annotation.

Example

import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api")
public class ApiController {

    @CrossOrigin(origins = "http://example.com")
    @GetMapping("/data")
    public String getData() {
        return "Data from API";
    }
}

In this example, the @CrossOrigin annotation is used to allow CORS to use the getData method, enabling requests from http://example.com.

Internal Working of CORS

When a browser makes a cross-origin HTTP request, it follows these steps.

  1. Preflight Request: For requests that might change server data (like POST, PUT, DELETE), the browser first sends an HTTP OPTIONS request to check if the actual request is safe to send. This is called a preflight request.
  2. CORS Headers: The server responds to the preflight request with CORS headers such as Access-Control-Allow-Origin, Access-Control-Allow-Methods, and Access-Control-Allow-Headers to specify the rules for the actual request.
  3. Actual Request: If the server allows the preflight request, the browser then sends the actual HTTP request.
  4. Server Response: The server responds to the actual request, and if the response includes the appropriate CORS headers, the browser allows the web page to access the response.

Detailed Workflow

  1. Client Side: A web application running on http://client.com tries to make an HTTP request to http://api.server.com/data.
  2. Preflight Request: The browser sends a preflight request.
    OPTIONS /data HTTP/1.1
    Host: api.server.com
    Origin: http://client.com
    Access-Control-Request-Method: GET
    Access-Control-Request-Headers: X-Custom-Header
    
  3. Server Response: The server responds with CORS headers.
    HTTP/1.1 200 OK
    Access-Control-Allow-Origin: http://client.com
    Access-Control-Allow-Methods: GET, POST, PUT, DELETE
    Access-Control-Allow-Headers: X-Custom-Header
    Access-Control-Allow-Credentials: true
    Access-Control-Max-Age: 3600
    
  4. Actual Request: The browser sends the actual request.
    GET /data HTTP/1.1
    Host: api.server.com
    Origin: http://client.com
    
  5. Server Response: The server responds to the actual request.
    HTTP/1.1 200 OK
    Access-Control-Allow-Origin: http://client.com
    Content-Type: application/json
    
    {
        "data": "Data from API"
    }
    
  6. Browser Access: The browser allows the web application to access the response because the CORS headers permit it.

Real-World Example

Consider a scenario where you have a Spring Boot backend API that serves data to a frontend application hosted on a different domain.

Backend API Configuration

WebConfig.java

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebConfig {

    @Bean
    public WebMvcConfigurer corsConfigurer() {
        return new WebMvcConfigurer() {
            @Override
            public void addCorsMappings(CorsRegistry registry) {
                registry.addMapping("/**")
                        .allowedOrigins("http://frontend.com")
                        .allowedMethods("GET", "POST", "PUT", "DELETE")
                        .allowedHeaders("*")
                        .allowCredentials(true)
                        .maxAge(3600);
            }
        };
    }
}

ApiController.java

import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api")
public class ApiController {

    @CrossOrigin(origins = "http://frontend.com")
    @GetMapping("/data")
    public String getData() {
        return "Data from API";
    }
}

Frontend Request Example (JavaScript)

fetch('http://api.server.com/api/data', {
    method: 'GET',
    credentials: 'include'
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));

Conclusion

CORS is an essential security feature that helps protect web applications from cross-origin attacks while allowing legitimate cross-origin requests. Spring Boot provides flexible and easy-to-use configurations for handling CORS, whether through global settings or per-controller annotations. By understanding the internal workings of CORS and properly configuring it, you can ensure that your applications are both secure and functional across different domains.