Springboot endpoint 403 OPTIONS when doing a POST request

If we are talking about SpringBoot, presumably latest or at least recent version, then we can simply use the @CrossOrigin annotation next to the @RestController annotation in our controller classes. It is available since Spring ver. 4.2

For example:

@RestController
@CrossOrigin
@RequestMapping("/api")
public class MyObjectsController {
    private final MyObjectsService service;

    @Autowired
    public MyObjectsController(MyObjectsService service) {
        this.service = service;
    }

    @GetMapping("/version")
    public Version getVersion() {
        return service.getVersion();
    }

    @PostMapping("/objects")
    public ObjectResource createObject(@RequestBody @Valid ObjectData data) {
        return service.createObject(data);
    }

    @GetMapping("/objects/{id}")
    public ObjectResource getObject(@PathVariable String id) {
        return service.getObjectById(id);
    }
}

Benefits:

  • annotated controller behaves a way better (and smarter) than any kind of self-written filters
  • it is also more flexible than a fixed CORS configuration for the whole project, since you can control what part of your API should support CORS headers and what should be only available for server-to-server communication
  • only methods supported by controller will be declared and allowed in the response to OPTIONS request
  • CORS headers will only be present in the response to CORS requests (i.e. presence of Referer header is analyzed)
  • etc.

See also:

  • Enabling Cross Origin Requests for a RESTful Web Service
  • CrossOrigin Annotation javadoc

To fix all CORS issues in an Angular(front end) plus Spring boot(backend) project, add the following Spring component:

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.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class ConfigCtrl implements Filter {
    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
        final HttpServletResponse response = (HttpServletResponse) res;
        response.setHeader("Access-Control-Allow-Origin", "*");
        response.setHeader("Access-Control-Allow-Methods", "POST, PUT, GET, OPTIONS, DELETE");
        response.setHeader("Access-Control-Allow-Headers", "Authorization, Content-Type");
        response.setHeader("Access-Control-Max-Age", "3600");
        if ("OPTIONS".equalsIgnoreCase(((HttpServletRequest) req).getMethod())) {
            response.setStatus(HttpServletResponse.SC_OK);
        } else {
            chain.doFilter(req, res);
        }
    }
    @Override
        public void destroy() {
    }
    @Override
        public void init(FilterConfig config) throws ServletException {
    }
}

PS for beginners: The name of the class and its location within the Spring app does not matter.
Credit to Ajitesh Kumar.


CORS Request is made by your Frontend to see what are the methods (HTTP Verbs) that your backed allows. This is usually required for monetary operations e.g., POST or PUT which are meant to modify data.

Hence your Frontend will make this call first and your backend needs to respond with allowed methods, you can also restrict specific URIs, then upon successful validation, the target call is made.

This is perfectly normal and angular does this internally so as to not make an unnecessary data request without knowing whether the server will allow.

Here's how you will set it up in Spring.

    //Change/Customize as necessary
    @Bean
    CorsConfigurationSource corsConfigurationSource() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        CorsConfiguration corsConfiguration = new CorsConfiguration();
        corsConfiguration.addAllowedOrigin("<your origin>");
        corsConfiguration.setAllowedMethods(Arrays.asList(
                HttpMethod.GET.name(),
                HttpMethod.HEAD.name(),
                HttpMethod.POST.name(),
                HttpMethod.PUT.name(),
                HttpMethod.DELETE.name()));
        corsConfiguration.setMaxAge(1800L);
        source.registerCorsConfiguration("/**", corsConfiguration); // you restrict your path here
        return source;
    }

If you are also using any custom response headers from your backend, then you need to allow that as well in the CORS configuration. As an example

    corsConfiguration.addAllowedHeader("*");
    corsConfiguration.addExposedHeader("header1");
    corsConfiguration.addExposedHeader("header2");