Nickknack
Nickknack

Reputation: 857

Springboot endpoint 403 OPTIONS when doing a POST request

I'm running a service using Spring and my Angular front-end is getting a 403 with Request Method: OPTIONS when it tries to make a POST request.

Both the Spring service and the Angular app are running locally on my machine. I tried toggling CORS with a Chrome plugin, but that didn't seem to fix the issue.

All my GET requests to the service seem to work alright. I can do the POST request in Postman, so I'm not sure why the angular app can't make the request, but Postman can.

****EDIT****

Response Headers

Allow: GET, HEAD, POST, PUT, DELETE, OPTIONS, PATCH
Content-Length: 20
Date: Sat, 31 Mar 2018 19:15:01 GMT

Request Headers

Accept: */*
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Access-Control-Request-Headers: content-type
Access-Control-Request-Method: POST
Connection: keep-alive
Host: localhost:9901
Origin: http://localhost:4200
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.162 Safari/537.36

Upvotes: 17

Views: 38008

Answers (5)

Teela
Teela

Reputation: 153

In my case, the issue was with the patters accepted in header, it was

cn.app.filters.cors-filter.allowed-path-pattern=/appname/**

Corrected it to

cn.app.filters.cors-filter.allowed-path-pattern=/**

Hope this helps!

Upvotes: 0

Priyanshu Parate
Priyanshu Parate

Reputation: 59

Always remember when we are configuring token base authentication in spring boot then for cors we have to configure in configure( HttpSecurity http){} method* otherwise, preflight requests get 403

@Override
protected void configure(HttpSecurity http) throws Exception {
http.addFilterAt(tokenAuthFilter(), 
BasicAuthenticationFilter.class);
//this is configuratio of cors
CorsConfiguration config = new CorsConfiguration();
config.addAllowedOrigin("*");
    config.addAllowedHeader("*");
    config.addAllowedMethod("*");
    config.setAllowCredentials(false);
   http.csrf().disable()
   //and this way we can setup configuration
   .cors().configurationSource((option)->{

               return config;
           })
  .and()
  .authorizeHttpRequests()
           
  .antMatchers("/api/user/create","/api/user/authenticate","/v3/api- 
  docs/**","/v3/swagger**/**", "/v3/swagger-ui.html", "/v3/swagger- 
  ui/**")
           .permitAll()
           .anyRequest()
           .authenticated();
}

Upvotes: -1

Vladimir L.
Vladimir L.

Reputation: 1126

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:

Upvotes: 6

Abubakar Ahmad
Abubakar Ahmad

Reputation: 2583

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.

Upvotes: 8

bitscanbyte
bitscanbyte

Reputation: 700

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

Upvotes: 14

Related Questions