Published on

How to Fix CORS Issue in Spring Boot

Authors

In a traditional Single Page Application, we'll be running two different servers for both API and UI which can be accessed via different port number (localhost) during development or different domain (production).

Since the API server's origin will be different from the UI server's origin. We'll be running into CORS issue in the browser.

What is CORS?

Cross Origin Resource Sharing (CORS) it is a security feature implemented in browser to block any cross origin requests (different domains/sub-domains or even ports). And the server in which we're making request has to explictly allow such request.

How it works?

Whenever cross origin request is made in browser, it'll make a "preflight" request to the server which has the cross-origin resource like a API endpoint and checks whether the current web page has permission to access it. If not, then it'll block the actual request like an API call.

You can read more about it in MDN docs

How to allow particular origin in Spring Boot?

There are multiple ways to allow origins in spring boot. Here are some of most common way to do it.

Approach #1: Allow it using @CrossOrigin

In your controller you can add @CrossOrigin at the top and declare the origins and allowed methods (optional) in it.

Like this

// HelloController.java
@CrossOrigin(
    origins = {
        "http://localhost:3000", 
        "https://staging.example.com", 
        "https://app.example.com"
        },
    methods = {
                RequestMethod.OPTIONS,
                RequestMethod.GET,
                RequestMethod.PUT,
                RequestMethod.DELETE,
                RequestMethod.POST
})
@RestController
public class ContactsController {
    // ...
}

Approach #2: Using Spring Boot Filter

The problem with the approach #1 is that you'll need to explictly declare it in the places that you needed. For one off cases that is fine.

However for a real world SPA apps, we might need to declare it in a single place and that should take care of CORS for all the endpoint.

For such cases, we can use Spring Boot Filter.

You can think of filter as an interceptor. And using that you can modify all your responses. The approach is similar to that of Express middleware. You have access to request and responses and you can modify things globally.

In your project, create a new file like SimpleCORSFilter.java

package com.example;

import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

@Component
public class SimpleCORSFilter implements Filter {

    private final Logger log = LoggerFactory.getLogger(SimpleCORSFilter.class);

    public SimpleCORSFilter() {
        log.info("SimpleCORSFilter init");
    }

    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {

        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) res;
        response.setHeader("Access-Control-Allow-Origin", "http://localhost:3000");
        response.setHeader("Access-Control-Allow-Credentials", "true");
        response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE, PUT");
        response.setHeader("Access-Control-Max-Age", "3600");
        response.setHeader("Access-Control-Allow-Headers", "Content-Type, Accept, X-Requested-With, x-customer-header-1, x-customer-header-2");

        chain.doFilter(req, res);
    }

    @Override
    public void init(FilterConfig filterConfig) {
    }

    @Override
    public void destroy() {
    }
}

You can even access the request origin to set value for "Access-Control-Allow-Origin" header.

Like this

    response.setHeader("Access-Control-Allow-Origin", request.getHeader("Origin"));

References