112

I am trying to call REST endpoints on one application (spring-boot application) from another (angularjs). The applications are running on the following hosts and ports.

  • REST application, using spring boot, http://localhost:8080
  • HTML application, using angularjs, http://localhost:50029

I am also using spring-security with the spring-boot application. From the HTML application, I can authenticate to the REST application, but, thereafter, I still cannot access any REST endpoint. For example, I have an angularjs service defined as follows.

adminServices.factory('AdminService', ['$resource', '$http', 'conf', function($resource, $http, conf) {
    var s = {};
    s.isAdminLoggedIn = function(data) {
        return $http({
            method: 'GET',
            url: 'http://localhost:8080/api/admin/isloggedin',
            withCredentials: true,
            headers: {
                'X-Requested-With': 'XMLHttpRequest'
            }
        });
    };
    s.login = function(username, password) {
        var u = 'username=' + encodeURI(username);
        var p = 'password=' + encodeURI(password);
        var r = 'remember_me=1';
        var data = u + '&' + p + '&' + r;

        return $http({
            method: 'POST',
            url: 'http://localhost:8080/login',
            data: data,
            headers: {'Content-Type': 'application/x-www-form-urlencoded'}
        });
    };
    return s;
}]);

The angularjs controller looks like the following.

adminControllers.controller('LoginController', ['$scope', '$http', 'AdminService', function($scope, $http, AdminService) {
    $scope.username = '';
    $scope.password = '';

    $scope.signIn = function() {
        AdminService.login($scope.username, $scope.password)
            .success(function(d,s) {
                if(d['success']) {
                    console.log('ok authenticated, call another REST endpoint');
                    AdminService.isAdminLoggedIn()
                        .success(function(d,s) {
                            console.log('i can access a protected REST endpoint after logging in');
                        })
                        .error(function(d, s) { 
                            console.log('huh, error checking to see if admin is logged in');
                            $scope.reset();
                        });
                } else {
                    console.log('bad credentials?');
                }
            })
            .error(function(d, s) {
                console.log('huh, error happened!');
            });
    };
}]);

On the call to http://localhost:8080/api/admin/isloggedin, I get a 401 Unauthorized.

On the REST application side, I have a CORS filter that looks like the following.

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

    @Override
    public void destroy() { }

    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
            throws IOException, ServletException {
        HttpServletResponse response = (HttpServletResponse) res;
        HttpServletRequest request = (HttpServletRequest) req;

        response.setHeader("Access-Control-Allow-Origin", "http://localhost:50029");
        response.setHeader("Access-Control-Allow-Methods", "POST, PUT, GET, OPTIONS, DELETE");
        response.setHeader("Access-Control-Max-Age", "3600");
        response.setHeader("Access-Control-Allow-Headers", "X-Requested-With, X-Auth-Token");
        response.setHeader("Access-Control-Allow-Credentials", "true");

        if(!"OPTIONS".equalsIgnoreCase(request.getMethod())) {
            chain.doFilter(req, res);
        }
    }

    @Override
    public void init(FilterConfig config) throws ServletException { }
}

My spring security configuration looks like the following.

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private RestAuthenticationEntryPoint restAuthenticationEntryPoint;

    @Autowired
    private JsonAuthSuccessHandler jsonAuthSuccessHandler;

    @Autowired
    private JsonAuthFailureHandler jsonAuthFailureHandler;

    @Autowired
    private JsonLogoutSuccessHandler jsonLogoutSuccessHandler;

    @Autowired
    private AuthenticationProvider authenticationProvider;

    @Autowired
    private UserDetailsService userDetailsService;

    @Autowired
    private PersistentTokenRepository persistentTokenRepository;

    @Value("${rememberme.key}")
    private String rememberMeKey;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .csrf().disable()
            .exceptionHandling()
            .authenticationEntryPoint(restAuthenticationEntryPoint)
                .and()
            .authorizeRequests()
                .antMatchers("/api/admin/**").hasRole("ADMIN")
                .antMatchers("/", "/admin", "/css/**", "/js/**", "/fonts/**", "/api/**").permitAll()
                .anyRequest().authenticated()
                .and()
            .formLogin()
                .successHandler(jsonAuthSuccessHandler)
                .failureHandler(jsonAuthFailureHandler)
                .permitAll()
                .and()
            .logout()
                .deleteCookies("remember-me", "JSESSIONID")
                .logoutSuccessHandler(jsonLogoutSuccessHandler)
                .permitAll()
                .and()
            .rememberMe()
                .userDetailsService(userDetailsService)
                .tokenRepository(persistentTokenRepository)
                .rememberMeCookieName("REMEMBER_ME")
                .rememberMeParameter("remember_me")
                .tokenValiditySeconds(1209600)
                .useSecureCookie(false)
                .key(rememberMeKey);
    }

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth
            .authenticationProvider(authenticationProvider);
    }
}

All the handlers are doing is writing out a JSON response like {success: true} based on if the user logged in, failed to authenticate, or logged out. The RestAuthenticationEntryPoint looks like the following.

@Component
public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint {

    @Override
    public void commence(HttpServletRequest req, HttpServletResponse resp, AuthenticationException ex)
            throws IOException, ServletException {
        resp.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
    }

}

Any ideas on what I am missing or doing wrong?

Jane Wayne
  • 8,205
  • 17
  • 75
  • 120
  • I suppose you need to carry the authentication as well, like a token or something. You have 2 servers. Did you look to that tutorial ?https://spring.io/guides/tutorials/spring-security-and-angular-js/ – Gokhan Oner Aug 31 '15 at 20:37
  • @GokhanOner How do I carry the authentication? That's probably the missing piece to this problem. Also, yes, I did go through those tutorials and didn't think they were aligned with my approach. First two parts dealt with Http-Basic authentication, then third part dealt with Redis (I did not want to or plan on getting that as a dependency), and then the last tutorial was about the `API Gateway` with spring cloud, which I thought was an overkill. – Jane Wayne Aug 31 '15 at 20:52
  • I suppose you can do it without the redis, it's just a key-value cache store. You need to store the authentication and CSRF token on a store, possible inside map on the fly. Key thing here is authentication key. look to the example :https://github.com/dsyer/spring-security-angular/tree/master/spring-session and the page with "resource server". You will see some additional beans defined, order of the CORS filter also important. And some prop. changes also necessary. – Gokhan Oner Aug 31 '15 at 21:06
  • Ok, I did a quick research. All you need, to get rid of Redis, is to crate a springSessionRepositoryFilter bean, look at https://github.com/spring-projects/spring-session/blob/1.0.0.RC1/spring-session/src/main/java/org/springframework/session/data/redis/config/annotation/web/http/RedisHttpSessionConfiguration.java, and also sessionRepository bean and in this bean, instead of RedisOperationsSessionRepository, you can use MapSessionRepository, which is also in spring-session. And then follow the example. – Gokhan Oner Aug 31 '15 at 21:24

20 Answers20

134
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

@Component
public class SimpleCORSFilter implements Filter {

private final Logger log = LoggerFactory.getLogger(SimpleCORSFilter.class);

public SimpleCORSFilter() {
    log.info("SimpleCORSFilter init");
}

@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {

    HttpServletRequest request = (HttpServletRequest) req;
    HttpServletResponse response = (HttpServletResponse) res;

    response.setHeader("Access-Control-Allow-Origin", request.getHeader("Origin"));
    response.setHeader("Access-Control-Allow-Credentials", "true");
    response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
    response.setHeader("Access-Control-Max-Age", "3600");
    response.setHeader("Access-Control-Allow-Headers", "Content-Type, Accept, X-Requested-With, remember-me");

    chain.doFilter(req, res);
}

@Override
public void init(FilterConfig filterConfig) {
}

@Override
public void destroy() {
}

}

No need extra define this filter just add this class. Spring will be scan and add it for you. SimpleCORSFilter. Here is the example: spring-enable-cors

abosancic
  • 1,776
  • 1
  • 15
  • 21
  • A few questions. 1) Where am I supposed to put the string constants `HEADERS` and `X_REDIRECT_LOCATION_HEADER`? 2) Is the line `request.getRequestURL());` a typo or copy/paste mistake? 3) Why do you not check for `OPTIONS` and simply continue with the filter chain? – Jane Wayne Aug 31 '15 at 22:19
  • 2
    But it blocks to execute AuthenticationEntryPoint.. Please guide – PAA Sep 22 '15 at 17:08
  • 1
    Thank You very much, it helped me awesomely in my struggle to get spring and ember to work together. Cheers mate! – Tomasz Szymanek Feb 15 '16 at 01:02
  • 1
    FindBugs doesn't like setting a header parameter with: `request.getHeader("Origin")` as shown above because of [HTTP response splitting](https://en.wikipedia.org/wiki/HTTP_response_splitting) – Glenn Dec 07 '16 at 13:43
  • 7
    If there are other filters in your application, this filter needs to be of highest precedence by annotating the filter with `@Order(Ordered.HIGHEST_PRECEDENCE) `. – Shafiul May 25 '17 at 08:22
  • for a problem similar to what @Prateek is talking about when using Spring Security - check my answer: https://stackoverflow.com/a/48633514/986160 – Michail Michailidis Feb 06 '18 at 00:09
  • Didn't work for me neither. Note that GET worked, POST not. The trick was to set the order of the Filter as @Shaful suggested: `@Order(Ordered.HIGHEST_PRECEDENCE)` – yglodt Sep 05 '18 at 22:38
  • 3
    This answers essentially bypasses the benefits of having CORS. By reflecting the Origin header into the "Access-control-allow-origin" response header and by allowing credentialed request, you are simply exposing your app for a cross site request from any site in the world. *Readers please use this answer with caution* – Shailesh Pratapwar Nov 07 '19 at 08:42
  • such a savior!! – Shubham Arya Oct 31 '20 at 06:14
  • 1
    Not working. Requests from :4200 to :8080 gets blocked. – Luca Pasini Jun 07 '21 at 16:02
  • if still not fix the issue please check this https://stackoverflow.com/a/67908572/1533783 – Khalid Habib Jun 09 '21 at 17:09
  • WORKS FOR ME, BUT PLEASE ADD PUT TO THE ALLOWED METHOD LIST. THANKS. – Daniel Rch. Aug 04 '22 at 03:00
55

I had been into the similar situation. After doing research and testing, here is my findings:

  1. With Spring Boot, the recommended way to enable global CORS is to declare within Spring MVC and combined with fine-grained @CrossOrigin configuration as:

     @Configuration
     public class CorsConfig {
    
         @Bean
         public WebMvcConfigurer corsConfigurer() {
             return new WebMvcConfigurerAdapter() {
                 @Override
                 public void addCorsMappings(CorsRegistry registry) {
                     registry.addMapping("/**").allowedMethods("GET", "POST", "PUT", "DELETE").allowedOrigins("*")
                             .allowedHeaders("*");
                 }
             };
         }
     }
    
  2. Now, since you are using Spring Security, you have to enable CORS at Spring Security level as well to allow it to leverage the configuration defined at Spring MVC level as:

     @EnableWebSecurity
     public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    
         @Override
         protected void configure(HttpSecurity http) throws Exception {
             http.cors().and()...
         }
     }
    

    Here is very excellent tutorial explaining CORS support in Spring MVC framework.

UPDATE (Sep 13, 2022): With latest version of Spring 5 and above, you have to use WebMvcConfigurer as below:

@EnableWebMvc
public class CorsConfig implements WebMvcConfigurer {
    @Override 
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
    }
}
Yogen Rai
  • 2,961
  • 3
  • 25
  • 37
  • 3
    ups it works with this change http .csrf() .disable() .cors() .and() – marti_ Nov 30 '17 at 22:03
  • 1
    @Osgux good to hear that :) since i am using JWT for authorization and they are csrf safe, i didn't put that there .. don't forget to upvote if it helped :) – Yogen Rai Nov 30 '17 at 22:27
  • @Marcel what issue u getting? – Yogen Rai Feb 14 '18 at 14:23
  • Failed to load : Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:8090' is therefore not allowed access. – Marcel Feb 14 '18 at 15:44
  • @Marcel how are u registering mapping in web config?? Also make sure to disable csrf in security config if not using jwt – Yogen Rai Feb 14 '18 at 15:47
  • If I add to CORS chrome plugin, the problem is solved. However, it's not practical. I need to solve using spring boot. – Marcel Feb 14 '18 at 15:47
  • @YogenRai I don't have a web config. I'm not using spring security. I have to disable csrf just if I'm using spring security. Isn't it? – Marcel Feb 14 '18 at 15:49
  • @Marcel did you look into the spring boot official tutorial I have mentioned on answer?? If you follow the steps there, you should be good. If still getting issue, let me know and if your repo is public, point me there, i can look into it – Yogen Rai Feb 14 '18 at 15:52
  • I tried there MyConfiguration class and it takes no effect. – Marcel Feb 14 '18 at 16:05
  • Still does not work in the case of error controller annotated with `@ControllerAdvice` see https://stackoverflow.com/q/43311952/906265 – Ivar Sep 05 '18 at 14:42
  • @AivarasPrudnikovas `@ControllerAdvice` is not for the controller, rather it is for handling cross-cutting concerns on controllers... – Yogen Rai Sep 05 '18 at 14:50
  • @Yogen Rai I get it and think `corsConfigurer` should automatically apply to those as well (responses I mean), otherwise It is not intuitive and hard to debug. – Ivar Sep 05 '18 at 16:47
  • yes, it does not work for me also. It has been deprecated now in Spring 5 – sarir Jul 23 '19 at 09:57
  • @Yogen Rai, your solution works for me, I am using Spring boot and Angular 8. – singhpradeep Jan 11 '20 at 03:13
  • @YogenRai WebMvcConfigurerAdapter is deprecated. what to use now – Amrit Raj Sep 08 '22 at 08:08
  • @AmritRaj thanks for pointing out, i have updated answer with latest version fix – Yogen Rai Sep 13 '22 at 18:48
34

If you want to enable CORS without using filters or without config file just add

@CrossOrigin

to the top of your controller and it work.

Eddie Martinez
  • 13,582
  • 13
  • 81
  • 106
14

To build on other answers above, in case you have a Spring boot REST service application (not Spring MVC) with Spring security, then enabling CORS via Spring security is enough (if you use Spring MVC then using a WebMvcConfigurer bean as mentioned by Yogen could be the way to go as Spring security will delegate to the CORS definition mentioned therein)

So you need to have a security config that does the following:

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

@Override
protected void configure(HttpSecurity http) throws Exception {
    //other http security config
    http.cors().configurationSource(corsConfigurationSource());
}

//This can be customized as required
CorsConfigurationSource corsConfigurationSource() {
    CorsConfiguration configuration = new CorsConfiguration();
    List<String> allowOrigins = Arrays.asList("*");
    configuration.setAllowedOrigins(allowOrigins);
    configuration.setAllowedMethods(singletonList("*"));
    configuration.setAllowedHeaders(singletonList("*"));
    //in case authentication is enabled this flag MUST be set, otherwise CORS requests will fail
    configuration.setAllowCredentials(true);
    UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
    source.registerCorsConfiguration("/**", configuration);
    return source;
}

}

This link has more information on the same: https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#cors

Note:

  1. Enabling CORS for all origins (*) for a prod deployed application may not always be a good idea.
  2. CSRF can be enabled via the Spring HttpSecurity customization without any issues
  3. In case you have authentication enabled in the app with Spring (via a UserDetailsService for example) then the configuration.setAllowCredentials(true); must be added

Tested for Spring boot 2.0.0.RELEASE (i.e., Spring 5.0.4.RELEASE and Spring security 5.0.3.RELEASE)

Deepak
  • 3,648
  • 1
  • 22
  • 17
  • This solved my problem. Being new to Spring and Spring Boot, I realized I wasn't building with Sring MVC. I had a Vue.js Client. Other answers seemed to be for Spring MVC, but this answer plugged in nicely with my already implemented authentication and authorization. – jaletechs Mar 24 '19 at 08:18
  • Hi @jaletechs , I am too using nuxtJs (a vuejs framework) but when it comes to setting cookie, its not working. would you be kind enough to help on this. – KAmit Jun 07 '19 at 18:42
10

Im using spring boot 2.1.0 and what worked for me was to

A. Add cors mappings by:

@Configuration
public class Config implements WebMvcConfigurer {
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**").allowedOrigins("*");
    }
}

B. Add below configuration to my HttpSecurity for spring security

.cors().configurationSource(new CorsConfigurationSource() {

    @Override
    public CorsConfiguration getCorsConfiguration(HttpServletRequest request) {
        CorsConfiguration config = new CorsConfiguration();
        config.setAllowedHeaders(Collections.singletonList("*"));
        config.setAllowedMethods(Collections.singletonList("*"));
        config.addAllowedOrigin("*");
        config.setAllowCredentials(true);
        return config;
    }
})

Also in case of a Zuul proxy you can use this INSTEAD OF A and B (just use HttpSecurity.cors() to enable it in Spring security):

@Bean
public CorsFilter corsFilter() {
    final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
    final CorsConfiguration config = new CorsConfiguration();
    config.setAllowCredentials(true);
    config.addAllowedOrigin("*");
    config.addAllowedHeader("*");
    config.addAllowedMethod("OPTIONS");
    config.addAllowedMethod("HEAD");
    config.addAllowedMethod("GET");
    config.addAllowedMethod("PUT");
    config.addAllowedMethod("POST");
    config.addAllowedMethod("DELETE");
    config.addAllowedMethod("PATCH");
    source.registerCorsConfiguration("/**", config);
    return new CorsFilter(source);
}
Sepehr GH
  • 1,297
  • 1
  • 17
  • 39
6

This works for me:

@Configuration
public class MyConfig extends WebSecurityConfigurerAdapter  {
   //...
   @Override
   protected void configure(HttpSecurity http) throws Exception {

       //...         

       http.cors().configurationSource(new CorsConfigurationSource() {

        @Override
        public CorsConfiguration getCorsConfiguration(HttpServletRequest request) {
            CorsConfiguration config = new CorsConfiguration();
            config.setAllowedHeaders(Collections.singletonList("*"));
            config.setAllowedMethods(Collections.singletonList("*"));
            config.addAllowedOrigin("*");
            config.setAllowCredentials(true);
            return config;
        }
      });

      //...

   }

   //...

}
  • While this code may answer the question, providing additional context regarding why and/or how this code answers the question improves its long-term value. – Adriano Martins Mar 26 '18 at 19:05
  • 1
    In case authentication is enabled via Spring security, then the config.setAllowCredentials(true); MUST be put otherwise CORS requests will still fail – Deepak Feb 13 '19 at 13:01
5

This is what worked for me.

@EnableWebSecurity
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {

        http.cors();
    }

}

@Configuration
public class WebConfiguration implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry
            .addMapping("/**")
            .allowedMethods("*")
            .allowedHeaders("*")
            .allowedOrigins("*")
            .allowCredentials(true);
    }

}
4

For me the only thing that worked 100% when spring security is used was to skip all the additional fluff of extra filters and beans and whatever indirect "magic" people kept suggesting that worked for them but not for me.

Instead just force it to write the headers you need with a plain StaticHeadersWriter:

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {

        http
            // your security config here
            .authorizeRequests()
            .antMatchers(HttpMethod.TRACE, "/**").denyAll()
            .antMatchers("/admin/**").authenticated()
            .anyRequest().permitAll()
            .and().httpBasic()
            .and().headers().frameOptions().disable()
            .and().csrf().disable()
            .headers()
            // the headers you want here. This solved all my CORS problems! 
            .addHeaderWriter(new StaticHeadersWriter("Access-Control-Allow-Origin", "*"))
            .addHeaderWriter(new StaticHeadersWriter("Access-Control-Allow-Methods", "POST, GET"))
            .addHeaderWriter(new StaticHeadersWriter("Access-Control-Max-Age", "3600"))
            .addHeaderWriter(new StaticHeadersWriter("Access-Control-Allow-Credentials", "true"))
            .addHeaderWriter(new StaticHeadersWriter("Access-Control-Allow-Headers", "Origin,Accept,X-Requested-With,Content-Type,Access-Control-Request-Method,Access-Control-Request-Headers,Authorization"));
    }
}

This is the most direct and explicit way I found to do it. Hope it helps someone.

Jeronimo Backes
  • 6,141
  • 2
  • 25
  • 29
  • along with the headers().., i included .cors().and().. before headers in the `@override configure()` method, to make it work for me. This is comparatively easy change. – Tim Apr 04 '22 at 06:08
2

Step 1

By annotating the controller with @CrossOrigin annotation will allow the CORS configurations.

@CrossOrigin
@RestController
public class SampleController { 
  .....
}

Step 2

Spring already has a CorsFilter even though You can just register your own CorsFilter as a bean to provide your own configuration as follows.

@Bean
public CorsFilter corsFilter() {
    final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
    final CorsConfiguration config = new CorsConfiguration();
    config.setAllowedOrigins(Collections.singletonList("http://localhost:3000")); // Provide list of origins if you want multiple origins
    config.setAllowedHeaders(Arrays.asList("Origin", "Content-Type", "Accept"));
    config.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "OPTIONS", "DELETE", "PATCH"));
    config.setAllowCredentials(true);
    source.registerCorsConfiguration("/**", config);
    return new CorsFilter(source);
}
Saveendra Ekanayake
  • 3,153
  • 6
  • 34
  • 44
1

If originally your program doesn't use spring security and can't afford for a code change, creating a simple reverse proxy can do the trick. In my case, I used Nginx with the following configuration:

http {
  server {
    listen 9090;
    location / {
      if ($request_method = 'OPTIONS') {
      add_header 'Access-Control-Allow-Origin' '*';
      add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
      #
      # Custom headers and headers various browsers *should* be OK with but aren't
      #
      add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
      #
      # Tell client that this pre-flight info is valid for 20 days
      #
      add_header 'Access-Control-Max-Age' 1728000;
      add_header 'Content-Type' 'text/plain; charset=utf-8';
      add_header 'Content-Length' 0;
      return 204;
      }
      if ($request_method = 'POST') {
      add_header 'Access-Control-Allow-Origin' '*';
      add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
      add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
      add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
      }
      if ($request_method = 'GET') {
      add_header 'Access-Control-Allow-Origin' '*';
      add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
      add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
      add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
      }

      proxy_pass http://localhost:8080;
    }
  }
}

My program listens to :8080.

REF: CORS on Nginx

Orvyl
  • 2,635
  • 6
  • 31
  • 47
1

In our Spring Boot app, we have set up CorsConfigurationSource like this.

Sequence of adding allowedOrigns first and then setting applyPermitDefaultValues() let Spring set up default values for allowed headers, exposed headers, allowed methods, etc. so we don't have to specify those.

    public CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration configuration = new CorsConfiguration();
        configuration.setAllowedOrigins(Arrays.asList("http://localhost:8084"));
        configuration.applyPermitDefaultValues();

        UrlBasedCorsConfigurationSource configurationSource = new UrlBasedCorsConfigurationSource();
        configurationSource.registerCorsConfiguration("/**", configuration);
        return configurationSource;
    }
    @Override
    protected void configure(HttpSecurity http) throws Exception {

        http.authorizeRequests()
                .antMatchers("/api/**")
                .access("@authProvider.validateApiKey(request)")
                .anyRequest().authenticated()
                .and().cors()
                .and().csrf().disable()
                .httpBasic().authenticationEntryPoint(authenticationEntryPoint);

        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
    }
Meet Shah
  • 750
  • 7
  • 5
0

check this one:

@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
    ...
            .antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
    ...
}
  • While this code may answer the question, providing additional context regarding why and/or how this code answers the question improves its long-term value. – rollstuhlfahrer Mar 04 '18 at 01:13
0

Extending WebSecurityConfigurerAdapter class and overriding configure() method in your @EnableWebSecurity class would work : Below is sample class

@Override
protected void configure(final HttpSecurity http) throws Exception {

         http
        .csrf().disable()
        .exceptionHandling();
         http.headers().cacheControl();

        @Override
        public CorsConfiguration getCorsConfiguration(final HttpServletRequest request) {
            return new CorsConfiguration().applyPermitDefaultValues();
        }
    });
   }
}
keyRen
  • 37
  • 1
  • 5
0

This answer copies the @abosancic answer but adds extra safety to avoid CORS exploit.

Tip 1: Do not reflect the incoming Origin as is without checking the list of allowed hosts to access.

Tip 2: Allow credentialed request only for whitelisted hosts.

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

@Component
public class SimpleCORSFilter implements Filter {

    private final Logger log = LoggerFactory.getLogger(SimpleCORSFilter.class);

    private List<String> allowedOrigins;

    public SimpleCORSFilter() {
        log.info("SimpleCORSFilter init");
        allowedOrigins = new ArrayList<>();
        allowedOrigins.add("https://mysafeorigin.com");
        allowedOrigins.add("https://itrustthissite.com");
    }

    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {

        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) res;

        String allowedOrigin = getOriginToAllow(request.getHeader("Origin"));

        if(allowedOrigin != null) {
            response.setHeader("Access-Control-Allow-Origin", allowedOrigin);
            response.setHeader("Access-Control-Allow-Credentials", "true");
        }

        response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
        response.setHeader("Access-Control-Max-Age", "3600");
        response.setHeader("Access-Control-Allow-Headers", "Content-Type, Accept, X-Requested-With, remember-me");

        chain.doFilter(req, res);
    }

    @Override
    public void init(FilterConfig filterConfig) {
    }

    @Override
    public void destroy() {
    }

    public String getOriginToAllow(String incomingOrigin) {
        if(allowedOrigins.contains(incomingOrigin.toLowerCase())) {
            return incomingOrigin;
        } else {
            return null;
        }
    }
}
Shailesh Pratapwar
  • 4,054
  • 3
  • 35
  • 46
0

Just Make a single class like, everything will be fine with this:

        @Component
        @Order(Ordered.HIGHEST_PRECEDENCE)
        public class MyCorsConfig 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, enctype");
                response.setHeader("Access-Control-Max-Age", "3600");
                if (HttpMethod.OPTIONS.name().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 {
            }
        }
Imranmadbar
  • 4,681
  • 3
  • 17
  • 30
0

This is what has worked for me in order to disable CORS between Spring boot and React

@Configuration
public class CorsConfig implements WebMvcConfigurer {

    /**
     * Overriding the CORS configuration to exposed required header for ussd to work
     *
     * @param registry CorsRegistry
     */

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedOrigins("*")
                .allowedMethods("*")
                .allowedHeaders("*")
                .allowCredentials(true)
                .maxAge(4800);
    }
}

I had to modify the Security configuration also like below:

        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.csrf().disable()
                    .cors().configurationSource(new CorsConfigurationSource() {

                @Override
                public CorsConfiguration getCorsConfiguration(HttpServletRequest request) {
                    CorsConfiguration config = new CorsConfiguration();
                    config.setAllowedHeaders(Collections.singletonList("*"));
                    config.setAllowedMethods(Collections.singletonList("*"));
                    config.addAllowedOrigin("*");
                    config.setAllowCredentials(true);
                    return config;
                }
            }).and()
                    .antMatcher("/api/**")
                    .authorizeRequests()
                    .anyRequest().authenticated()
                    .and().httpBasic()
                    .and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                    .and().exceptionHandling().accessDeniedHandler(apiAccessDeniedHandler());
        }
Gil Nz
  • 71
  • 6
0

I was suprised to only find Eduardo Dennis pointing to the up-to-date solution which is much simpler & doesn't involve the need to write your own Filter classes: It's using the

  • org.springframework.web.bind.annotation.CrossOrigin annotation on your Controllers
  • and including and().cors() to your Spring Security configuration.

That's all you have to do!

You can use the @CrossOrigin annotation like this:

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
@RequestMapping("/api")
@CrossOrigin
public class BackendController {
    ...
}

If you want to configure allowedHeaders, methods, origins and so on, you can simply add those values to the annotation like this: @CrossOrigin(origins = "http://localhost:50029", maxAge = 3600).

Using the @CrossOrigin annotation, the Spring Security configuration becomes extremely easy. Simply add and().cors() to your WebSecurityConfig.java class:

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .csrf().disable()
            .and().cors()
            ...
    }

That's all! You may delete your Filter/CORSFilter classes. If you want to add a global configuration, you can declare a CorsConfigurationSource also. See this great answer or this blog post by Sébastien Deleuze). There's also clearly stated by the Spring developers:

This approach supersedes the filter-based approach previously recommended.

Therefore the accepted answer is outdated. Here's also a fully working example project: https://github.com/jonashackt/microservice-api-spring-boot

jonashackt
  • 12,022
  • 5
  • 67
  • 124
  • This is wrong too. .cors() needs to go after disable, then .and() – Luca Pasini Jun 07 '21 at 15:59
  • Plus, even in the correct order, this is not working. blocks all my requests from localhost:4200 to localhost:8080 – Luca Pasini Jun 07 '21 at 16:01
  • Well maybe you can show us example code? "wrong" ist quite harsh here - since this advice comes directly from the Spring Development Team. And the implemented Builder Pattern shouldn't care about any order. Last but not least: I linked to an example project which shows a working solution with exaclty the code I described here, so maybe you should have a look into your code, if there's something different interfering with the Spring Security Configuration. – jonashackt Jun 21 '21 at 06:47
0

To enable CORS Globally you need to make changes in two places:

1. Spring Boot:

@Configuration
public class CorsConfiguration extends WebMvcConfigurationSupport {  
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**").allowedOrigins("*").allowedMethods("*")
        .allowCredentials(true);
    }
}

You can do the same in WebMvcConfigurerAdapter, or create bean of WebMvcConfigurer.

2. Spring Security

@Override
    protected void configure(HttpSecurity http) throws Exception {
        http.cors().and()
                .authorizeRequests()
                .antMatchers(HttpMethod.OPTIONS).permitAll() //Permits your preflight request
}

Works as on Spring Boot 2.3.3.RELEASE

Deb
  • 5,163
  • 7
  • 30
  • 45
0

The simple way is to create a bean in your spring boot application class(class with @SpringBootApplication) as below:

Note! i specified "http://localhost:4200" below on "setAllowedOrigins()" because am running the application on localhost and using angular default port.

@Bean
public CorsFilter corsFilter(){
    CorsConfiguration corsConfiguration = new CorsConfiguration();
    corsConfiguration.setAllowCredentials(true);
    corsConfiguration.setAllowedOrigins(Arrays.asList("http://localhost:4200"));
    corsConfiguration.setAllowedHeaders(Arrays.asList("Origin","Access-Control-Allow-Origin","Content-Type",
            "Accept", "Authorization", "Origin, Accept", "X-Requested-With",
            "Access-Control-Request-Method", "Access-Control-Request-Headers"));
    corsConfiguration.setExposedHeaders(Arrays.asList("Origin", "Content-Type", "Accept","Authorization",
            "Access-Control-Allow-Origin", "Access-Control-Allow-Origin", "Access-Control-Allow-Credentials"));
    corsConfiguration.setAllowedMethods(Arrays.asList("GET", "POST","PUT","DELETE","OPTIONS"));
    UrlBasedCorsConfigurationSource urlBasedCorsConfigurationSource = new UrlBasedCorsConfigurationSource();
    urlBasedCorsConfigurationSource.registerCorsConfiguration("/**", corsConfiguration);
    return new CorsFilter(urlBasedCorsConfigurationSource);
}
Tinyiko Chauke
  • 365
  • 2
  • 14
0
package tiny.url.urlshortner;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

@SpringBootApplication
public class UrlshortnerApplication {

    public static void main(String[] args) {
        SpringApplication.run(UrlshortnerApplication.class, args);
    }
    @Bean
    public WebMvcConfigurer corsConfigurer() {
        return new WebMvcConfigurerAdapter() {
            @Override
            public void addCorsMappings(CorsRegistry registry) {
                registry.addMapping("/**").allowedMethods("GET", "POST", "PUT", "DELETE").allowedOrigins("*")
                        .allowedHeaders("*");
            }
        };
    }
}
ADITYA AHLAWAT
  • 140
  • 1
  • 13