Cody Pritchard
Cody Pritchard

Reputation: 877

Spring Boot - CORS filter works for GET, but not for other HTTP Verbs

I am working in a Spring Boot 2.2.5 application with an Angular 9 frontend.

I have been trying to configure a CORS filter on the Spring Boot backend to allow any origin with any headers for any request.

Based on my research, what I currently have implemented in my main Application.java file should work:

@Bean
public CorsFilter corsFilter() {
    UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
    CorsConfiguration config = new CorsConfiguration();
    config.setAllowCredentials(true);
    config.addAllowedOrigin("*");
    config.addAllowedHeader("*");
    config.addAllowedMethod("OPTIONS");
    config.addAllowedMethod("GET");
    config.addAllowedMethod("POST");
    config.addAllowedMethod("PUT");
    config.addAllowedMethod("DELETE");
    source.registerCorsConfiguration("/**", config);
    return new CorsFilter(source);
}

However, what I am experiencing is that this is only allowing me to execute GET call from the frontend to my controller on the backend. Any other call to POST or PUT fails with the following error:

Access to XMLHttpRequest at 'http://localhost:8080/spring-boot-starter-seed/default-theme/save/cyan' from origin 'http://localhost:8083' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

I know im somewhat on the right track because if I remove the @Bean I implemented, then all calls from frontend to backend fail due to that error. But when implementing that Bean, for some reason its only working for GET requests.

Is there something I have missed?

***** Update *****

I dont know if this will be helpful, but, I am using Azure AD for authentication with this application. I have a WebSecurityConfig that looks like this:

@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Bean
    public JwtAuthenticationConverter jwtAuthenticationConverter() {
        JwtGrantedAuthoritiesConverter grantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
        grantedAuthoritiesConverter.setAuthoritiesClaimName("roles");
        grantedAuthoritiesConverter.setAuthorityPrefix("ROLE_");

        JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
        jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(grantedAuthoritiesConverter);
        return jwtAuthenticationConverter;
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
                .antMatchers("/actuator/**", "/heartbeat/**", "/register", "/unregister").permitAll()
                .anyRequest().authenticated()
                .and()
                .oauth2ResourceServer()
                .jwt()
                .jwtAuthenticationConverter(this.jwtAuthenticationConverter());
    }
}

Earlier in my post I said that GET requests are working, but that seems to be somewhat false.

GET /heartbeat works. However...

GET /default-theme does not work and fails with the same No Access-Control-Allow-Origin error.

The only difference between these two Controller paths is that the /default-theme is not included in the excluded antMatchers, and it is protected by @PreAuthorize(value = "hasRole('ROLE_access')")

Upvotes: 2

Views: 2771

Answers (3)

silentsudo
silentsudo

Reputation: 6963

Inside your WebSecurityConfig class you need to configure cors as given below

@Override
protected void configure(HttpSecurity http) throws Exception {
    http
     ...
     .and()
     .cors().configurationSource(corsConfigurationSource())
     .and()
     ...
}
@Bean
    public CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration configuration = new CorsConfiguration();
        configuration.setAllowedOrigins(Arrays.asList(
                "list of domains here"
                )
        );
        configuration.setAllowedMethods(Arrays.asList("DELETE", "GET", "POST", "PATCH", "PUT", "OPTIONS"));
        configuration.setAllowCredentials(true);
        configuration.setAllowedHeaders(
                Arrays.asList(
                        "Access-Control-Allow-Headers",
                        "Access-Control-Allow-Origin",
                        "Access-Control-Request-Method",
                        "Access-Control-Request-Headers",
                        "Origin", "Cache-Control",
                        "Content-Type",
                        "Authorization"));
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);
        return source;
    }

Upvotes: 1

Cody Pritchard
Cody Pritchard

Reputation: 877

So, as it turns out, my update on my original post was on the right track to assume that Spring Security and my WebSecurityConfig was part of the cause.

I found the real solution on an answer here.

I needed to update my HttpSecurity config to include the .cors() first:

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.cors().and()
            .authorizeRequests()
            .antMatchers("/actuator/**", "/heartbeat", "/register", "/unregister").permitAll()
            .anyRequest().authenticated()
            .and()
            .oauth2ResourceServer()
            .jwt()
            .jwtAuthenticationConverter(this.jwtAuthenticationConverter());
}

After doing this, all other CORS configurations were unnecessary and could be removed from my app. Just add the @CrossOrigin annotation to any relevant controllers.

With this in place, calling my GET /default-theme controller, protected by the @PreAuthorize(value = "hasRole('ROLE_access')"), got rid of the No Access-Control-Allow-Origin error, and resulted in the error I expected which was a 401 response.

The underlying reason for all of this was because I was tasked with migrating this application from LDAP auth to Azure AD auth.

Hopefully this will help someone in the future.

Upvotes: 0

Omar Amaoun
Omar Amaoun

Reputation: 526

I think the problem here is due to the filter of the dispatcher servlet :

try this class to implement a filter as well :

public class CORSFilter extends GenericFilterBean implements Filter {

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chaine)
            throws IOException, ServletException {
        HttpServletResponse httpResponse = (HttpServletResponse)response;
        httpResponse.setHeader("Access-Control-Allow-Origin", "*");
        httpResponse.setHeader("Access-Control-Allow-Methods", "*");
        httpResponse.setHeader("Access-Control-Allow-Headers", "*");
        httpResponse.setHeader("Access-Control-Allow-Credentials", "false");
        httpResponse.setHeader("Access-Control-Max-Age", "3600");
        System.out.println("****************** CORS Configuration Completed *******************");
        chaine.doFilter(request, response);
    }

}

Then inject the bean in your main application class or every where :

@Bean
    public FilterRegistrationBean crosFilterRegistration(){
        FilterRegistrationBean registrationBean = new FilterRegistrationBean(new CORSFilter());
        registrationBean.setName("CORS Filter");
        registrationBean.addUrlPatterns("/*");
        registrationBean.setOrder(1);
        return registrationBean;
    }

Hope this would be helpfull

Upvotes: 1

Related Questions