Search code examples
javaspring-bootgoogle-chromecorsprivate-network-access

Spring CORS Filter for Access-Control-Allow-Private-Network not working


I have a webapp hosted on a public URL, trying to call a local webservice API (running on localhost) I am using Chrome to access the public website, this creates a CORS issue as described here

https://developer.chrome.com/blog/private-network-access-preflight/

The solution stated in the above link is to either,

  1. Disable Private Network Access checks in Chrome (which works)
  2. Set response header "Access-Control-Allow-Private-Network: true" (doesnt work)

There is no Spring CORS support for the header "Access-Control-Allow-Private-Network" yet. So I have tried various ways to create a filter in my Spring Boot application, but to no avail. The filter never gets invoked and I encounter the CORS issue.

Please point me in the right direction to fix this issue.

This is my filter class

@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class MyFilter implements Filter {

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        log.info("MyFilter invoked............");
        final HttpServletResponse response = (HttpServletResponse) servletResponse;
        response.setHeader("Access-Control-Allow-Private-Network", "true");
        filterChain.doFilter(servletRequest, servletResponse);
    }
}

This is my config class

@Configuration
public class MyConfig {

    @Bean
    public WebMvcConfigurer corsMappingConfigurer() {
        return new WebMvcConfigurer() {
            @Override
            public void addCorsMappings(CorsRegistry registry) {
                registry.addMapping("/**");
            }
        };
    }

Solution

  • This is not quite an answer to why your code does not work, but I have just created a Spring way to achieve this, so it may assist you. I am using Spring Boot 2.7 therefore Spring 5.3 is included.

    It uses a custom CorsProcessor class to add the relevant header to the response.

    CustomCorsProcessor.java:

    public class CustomCorsProcessor extends DefaultCorsProcessor implements CorsProcessor {
    
        private static final String ACCESS_CONTROL_REQUEST_PRIVATE_NETWORK = "Access-Control-Request-Private-Network";
        private static final String ACCESS_CONTROL_ALLOW_PRIVATE_NETWORK = "Access-Control-Allow-Private-Network";
        
        @Override
        public boolean processRequest(@Nullable CorsConfiguration config, HttpServletRequest request,
                HttpServletResponse response) throws IOException {
            
            //Allow DefaultCorsProcessor to run first
            boolean superResult = super.processRequest(config, request, response);
            if (superResult == false) return false;
            
            ServerHttpRequest serverRequest = new ServletServerHttpRequest(request);
            
            //If the CORS header requesting Private Network access is present, respond allowing access
            if(serverRequest.getHeaders().containsKey(ACCESS_CONTROL_REQUEST_PRIVATE_NETWORK)) {
                response.addHeader(ACCESS_CONTROL_ALLOW_PRIVATE_NETWORK, Boolean.toString(true));
            }
            
            return true;
        }
    }
    

    You might like to customise the CorsConfiguration class to avoid hardcoding the Boolean true.

    Then the CustomCorsProcessor class is used in WebSecurityConfig as a CorsProcessor.

    Note that before this Private Network change, the CorsConfigurationSource was itself an @Bean but now the CorsFilter is the @Bean.

    @Configuration
    @EnableWebSecurity
    public class WebSecurityConfig {
        @Bean
        public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
            http
                // by default uses a Bean by the name of corsFilter
                .cors()
                .and()
                ... as needed for your application
    
            return http.build();
        }
    
        public CorsConfigurationSource corsConfigurationSource() {
            CorsConfiguration configuration = new CorsConfiguration();
            configuration.setAllowCredentials(true);
            configuration.addAllowedOrigin("http://domain1.com");
            configuration.addAllowedHeader("*");
            configuration.addAllowedMethod("*");
            UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
            source.registerCorsConfiguration("/**", configuration);
            return source;
        }
        
        @Bean
        public CorsFilter corsFilter() {
            CorsConfigurationSource corsConfigurationSource = corsConfigurationSource();
            CorsFilter corsFilter = new CorsFilter(corsConfigurationSource);
    
            //Register our custom CorsProcessor that includes the Private Network allowed header
            corsFilter.setCorsProcessor(new CustomCorsProcessor());
            return corsFilter;
        }
    }
    

    This then allowed Chrome to connect to the Private Network web service.

    This is partly based on what I found from these URLs:

    https://spring.io/blog/2015/06/08/cors-support-in-spring-framework
    https://www.baeldung.com/spring-cors