'Spring Boot security can not disable CSRF protection

My Spring Boot REST API is protected by a Keycloak instance. Since the CSRF protection only allows GET and POST, I want to disable it. However, my approach does not seem to work since the REST API will return HTTP status 403 for any request with an origin that differs from http://localhost:8080. Here is how I configure my security:

package de.longnguyen.security;

import de.longnguyen.controller.KeycloakController;
import org.keycloak.adapters.springboot.KeycloakSpringBootConfigResolver;
import org.keycloak.adapters.springsecurity.KeycloakSecurityComponents;
import org.keycloak.adapters.springsecurity.authentication.KeycloakAuthenticationProvider;
import org.keycloak.adapters.springsecurity.config.KeycloakWebSecurityConfigurerAdapter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.authority.mapping.SimpleAuthorityMapper;
import org.springframework.security.core.session.SessionRegistryImpl;
import org.springframework.security.web.authentication.session.RegisterSessionAuthenticationStrategy;
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
import org.springframework.security.web.session.SessionManagementFilter;


@Configuration
@EnableWebSecurity
@ComponentScan(basePackageClasses = KeycloakSecurityComponents.class)
public class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter {
    private static final String[] ALLOWED = new String[]{"/", "/static/**", "/v2/api-docs", "/swagger*/**", "/webjars/**"};

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) {
        KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider();
        keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper());
        auth.authenticationProvider(keycloakAuthenticationProvider);
    }

    @Bean
    public KeycloakSpringBootConfigResolver KeycloakConfigResolver() {
        return new KeycloakSpringBootConfigResolver();
    }

    @Bean
    @Override
    protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
        return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl());
    }

    @Override
    public void configure(WebSecurity web) throws Exception {
        super.configure(web);
        web.ignoring()
                .antMatchers(ALLOWED);
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        super.configure(http);
        http
                .cors()
                .and()
                .authorizeRequests()
                .antMatchers(ALLOWED).permitAll()
                .anyRequest()
                .authenticated()
                .and()
                .addFilterBefore(new CustomCorsFilter(), SessionManagementFilter.class)
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .csrf().disable();
    }
}

A request with Origin of http://localhost:8080 will works:

enter image description here

However the exact same request with an origin of http://localhost:3000 will not work: enter image description here

EDIT:

This is how My CustomCorsFilter looks like:

package de.longnguyen.security;

import lombok.extern.slf4j.Slf4j;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;

@Slf4j
public class CustomCorsFilter implements Filter {

    public void destroy() {
    }

    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
        if (req instanceof HttpServletRequest && res instanceof HttpServletResponse) {
            HttpServletRequest request = (HttpServletRequest) req;
            HttpServletResponse response = (HttpServletResponse) res;
            response.setHeader("Access-Control-Allow-Origin", request.getHeader("Origin"));
            response.setHeader("Vary", "Origin");
            response.setHeader("Access-Control-Max-Age", "3600");
            response.setHeader("Access-Control-Allow-Credentials", "true");
            response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE, PUT");
            response.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, X-CSRF-TOKEN");
        }
        chain.doFilter(req, res);
    }

    public void init(FilterConfig filterConfig) {
    }
}

Edit edit:

This is how my logs look like:

2020-03-26 23:08:45.758 DEBUG 27582 --- [nio-8080-exec-2] o.s.s.w.u.matcher.AntPathRequestMatcher  : Checking match of request : '/api/v1/keycloak/delete'; against '/'
2020-03-26 23:08:45.758 DEBUG 27582 --- [nio-8080-exec-2] o.s.s.w.u.matcher.AntPathRequestMatcher  : Checking match of request : '/api/v1/keycloak/delete'; against '/static/**'
2020-03-26 23:08:45.758 DEBUG 27582 --- [nio-8080-exec-2] o.s.s.w.u.matcher.AntPathRequestMatcher  : Checking match of request : '/api/v1/keycloak/delete'; against '/v2/api-docs'
2020-03-26 23:08:45.758 DEBUG 27582 --- [nio-8080-exec-2] o.s.s.w.u.matcher.AntPathRequestMatcher  : Checking match of request : '/api/v1/keycloak/delete'; against '/swagger*/**'
2020-03-26 23:08:45.758 DEBUG 27582 --- [nio-8080-exec-2] o.s.s.w.u.matcher.AntPathRequestMatcher  : Checking match of request : '/api/v1/keycloak/delete'; against '/webjars/**'
2020-03-26 23:08:45.758 DEBUG 27582 --- [nio-8080-exec-2] o.s.security.web.FilterChainProxy        : /api/v1/keycloak/delete at position 1 of 17 in additional filter chain; firing Filter: 'WebAsyncManagerIntegrationFilter'
2020-03-26 23:08:45.758 DEBUG 27582 --- [nio-8080-exec-2] o.s.security.web.FilterChainProxy        : /api/v1/keycloak/delete at position 2 of 17 in additional filter chain; firing Filter: 'SecurityContextPersistenceFilter'
2020-03-26 23:08:45.758 DEBUG 27582 --- [nio-8080-exec-2] o.s.security.web.FilterChainProxy        : /api/v1/keycloak/delete at position 3 of 17 in additional filter chain; firing Filter: 'HeaderWriterFilter'
2020-03-26 23:08:45.758 DEBUG 27582 --- [nio-8080-exec-2] o.s.security.web.FilterChainProxy        : /api/v1/keycloak/delete at position 4 of 17 in additional filter chain; firing Filter: 'CorsFilter'
2020-03-26 23:08:45.759 DEBUG 27582 --- [nio-8080-exec-2] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to public org.springframework.http.ResponseEntity<java.lang.String> de.longnguyen.controller.KeycloakController.delete()
2020-03-26 23:08:45.760 DEBUG 27582 --- [nio-8080-exec-2] o.s.web.cors.DefaultCorsProcessor        : Reject: HTTP 'DELETE' is not allowed
2020-03-26 23:08:45.761 DEBUG 27582 --- [nio-8080-exec-2] o.s.s.w.header.writers.HstsHeaderWriter  : Not injecting HSTS header since it did not match the requestMatcher org.springframework.security.web.header.writers.HstsHeaderWriter$SecureRequestMatcher@3fffd0ea
2020-03-26 23:08:45.761 DEBUG 27582 --- [nio-8080-exec-2] s.s.w.c.SecurityContextPersistenceFilter : SecurityContextHolder now cleared, as request processing completed


Solution 1:[1]

@LongNguyen that is incorrect. Whenever you send a cross domain request from the browser, the browser sends a preflight request to the server to read Access-Conrol-Allow-Origin header. If no response headers are set from the server, the browser do not send your actual request. The trick people use is they leave the server alone and implement a CORS middleware on the client side. Like a HTTPInterceptor in an angular application which adds a Access-Control-Allow-Origin= * response header and tricks the browser in believing the header came from the server.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Malwaregeek