Проблема CORS - в запрашиваемом ресурсе отсутствует заголовок "Access-Control-Allow-Origin"

Я создал два веб-приложения - клиентские и сервисные приложения.
Взаимодействие между клиентскими и сервисными приложениями проходит нормально, когда они развернуты в одном экземпляре Tomcat.
Но когда приложения развертываются в отдельных экземплярах Tomcat (на разных машинах), я получаю следующую ошибку при запросе на отправку приложения-службы.

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:8080' is therefore not allowed access. The response had HTTP status code 401

Мое клиентское приложение использует JQuery, HTML5 и Bootstrap.

AJAX вызов сделан на обслуживание, как показано ниже:

var auth = "Basic " + btoa({usname} + ":" + {password});
var service_url = {serviceAppDomainName}/services;

if($("#registrationForm").valid()){
    var formData = JSON.stringify(getFormData(registrationForm));
    $.ajax({
        url: service_url+action,
        dataType: 'json',
        async: false,
        type: 'POST',
        headers:{
            "Authorization":auth
        },
        contentType: 'application/json',
        data: formData,
        success: function(data){
            //success code
        },
        error: function( jqXhr, textStatus, errorThrown ){
            alert( errorThrown );
        });
}

Мое сервисное приложение использует Spring MVC, Spring Data JPA и Spring Security.

Я включил CorsConfiguration класс, как показано ниже:

CORSConfig.java:

@Configuration
@EnableWebMvc
public class CORSConfig extends WebMvcConfigurerAdapter  {
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("*");
    }
}

SecurityConfig.java:

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
@EnableWebSecurity
@ComponentScan(basePackages = "com.services", scopedProxy = ScopedProxyMode.INTERFACES)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    @Qualifier("authenticationService")
    private UserDetailsService userDetailsService;

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService);
        auth.authenticationProvider(authenticationProvider());
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
       http
                .authorizeRequests()
                .antMatchers("/login").permitAll()
                .anyRequest().fullyAuthenticated();
        http.httpBasic();
        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
        http.csrf().disable();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public DaoAuthenticationProvider authenticationProvider() {
        DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
        authenticationProvider.setUserDetailsService(userDetailsService);
        authenticationProvider.setPasswordEncoder(passwordEncoder());
        return authenticationProvider;
    }
}

Зависимости Spring Security:

 <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-config</artifactId>
            <version>3.2.3.RELEASE</version>
</dependency>
<dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-web</artifactId>
            <version>3.2.3.RELEASE</version>
</dependency>

Я использую сервер Apache Tomcat для развертывания.

12 ответов

Решение

Предварительный запрос CORS использует HTTP OPTIONS без учетных данных, см. Обмен ресурсами между источниками:

В противном случае сделайте предварительный запрос. Извлечь URL-адрес запроса из источника источника источника, используя источник реферера в качестве источника источника переопределения с установленным флагом ручного перенаправления и флагом куки-файлов блока, используя метод OPTIONS и следующие дополнительные ограничения:

  • Включите заголовок Access-Control-Request-Method со значением поля заголовка в метод запроса (даже если это простой метод).
  • Если заголовки запроса автора не пусты, включите заголовок Access-Control-Request-Headers со значением поля заголовка в виде разделенного запятыми списка имен полей заголовка из заголовков запроса автора в лексикографическом порядке, каждый из которых преобразуется в нижний регистр ASCII (даже когда один или более простой заголовок).
  • Исключить заголовки запроса автора.
  • Исключить учетные данные пользователя.
  • Исключить тело запроса.

Вы должны разрешить анонимный доступ для HTTP OPTIONS,

Ваш измененный (и упрощенный) код:

@Override
protected void configure(HttpSecurity http) throws Exception {
   http
       .authorizeRequests()
           .andMatchers(HttpMethod.OPTIONS, "/**").permitAll()
           .antMatchers("/login").permitAll()
           .anyRequest().fullyAuthenticated()
           .and()
       .httpBasic()
           .and()
       .sessionManagement()
           .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
           .and()
       .csrf().disable();
}

Начиная с Spring Security 4.2.0 вы можете использовать встроенную поддержку, см. Spring Security Reference:

19. CORS

Spring Framework обеспечивает первоклассную поддержку CORS. CORS должен быть обработан до Spring Security, так как предполетный запрос не будет содержать куки (т.е. JSESSIONID). Если запрос не содержит файлов cookie и Spring Security является первым, запрос определит, что пользователь не прошел проверку подлинности (поскольку в запросе нет файлов cookie), и отклонит его.

Самый простой способ убедиться, что CORS обрабатывается первым - это использовать CorsFilter, Пользователи могут интегрировать CorsFilter с Spring Security, предоставляя CorsConfigurationSource используя следующее:

@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

  @Override
  protected void configure(HttpSecurity http) throws Exception {
      http
          // by default uses a Bean by the name of corsConfigurationSource
          .cors().and()
          ...
  }

  @Bean
  CorsConfigurationSource corsConfigurationSource() {
      CorsConfiguration configuration = new CorsConfiguration();
      configuration.setAllowedOrigins(Arrays.asList("https://example.com"));
      configuration.setAllowedMethods(Arrays.asList("GET","POST"));
      UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
      source.registerCorsConfiguration("/**", configuration);
      return source;
  }
}

Начиная с Spring Security 4.1, это правильный способ заставить Spring Security поддерживать CORS (также необходим в Spring Boot 1.4/1.5):

@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedMethods("HEAD", "GET", "PUT", "POST", "DELETE", "PATCH");
    }
}

а также:

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
//        http.csrf().disable();
        http.cors();
    }

    @Bean
    public CorsConfigurationSource corsConfigurationSource() {
        final CorsConfiguration configuration = new CorsConfiguration();
        configuration.setAllowedOrigins(ImmutableList.of("*"));
        configuration.setAllowedMethods(ImmutableList.of("HEAD",
                "GET", "POST", "PUT", "DELETE", "PATCH"));
        // setAllowCredentials(true) is important, otherwise:
        // The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include'.
        configuration.setAllowCredentials(true);
        // setAllowedHeaders is important! Without it, OPTIONS preflight request
        // will fail with 403 Invalid CORS request
        configuration.setAllowedHeaders(ImmutableList.of("Authorization", "Cache-Control", "Content-Type"));
        final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);
        return source;
    }
}

Не делайте ничего из нижеприведенного, что является неправильным способом решения проблемы:

  • http.authorizeRequests().antMatchers(HttpMethod.OPTIONS, "/**").permitAll();
  • web.ignoring().antMatchers(HttpMethod.OPTIONS);

Ссылка: http://docs.spring.io/spring-security/site/docs/4.2.x/reference/html/cors.html

Добавьте приведенную ниже конфигурацию в основное приложение. Это сработало у меня в весеннем загрузочном приложении 2.3.1

      package com.example.restservicecors;

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;

@SpringBootApplication
public class RestServiceCorsApplication {

    public static void main(String[] args) {
        SpringApplication.run(RestServiceCorsApplication.class, args);
    }

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

}

Справочный источник : https://spring.io/guides/gs/rest-service-cors/

В моем случае у меня есть Resource Server с включенной защитой OAuth, и ни одно из перечисленных выше решений не работает. После некоторой отладки и поиска в Google понял, почему.

@Bean
public FilterRegistrationBean corsFilter() {
    UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
    CorsConfiguration config = new CorsConfiguration();
    config.setAllowCredentials(true);
    config.addAllowedOrigin("*");
    config.addAllowedHeader("*");
    config.addAllowedMethod("*");
    source.registerCorsConfiguration("/**", config);
    FilterRegistrationBean bean = new FilterRegistrationBean(new CorsFilter(source));
    bean.setOrder(Ordered.HIGHEST_PRECEDENCE);
    return bean;
}

В основном в этом примере Ordered.HIGHEST_PRECEDENCE это ключ!

https://github.com/spring-projects/spring-security-oauth/issues/938

Различные зависимости POM добавляют различные виды фильтров, и поэтому у нас могут быть проблемы в зависимости от порядка.

Если вы используете angular с загрузкой Spring. Это конфигурация, которая сработала для меня

      @Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http.csrf().disable().cors().disable()
            .authorizeHttpRequests()
            .requestMatchers(HttpMethod.OPTIONS).permitAll() // Angular httpClient uses OPTIONS first in all requests
            .anyRequest()
            .authenticated()
            .and()
            .httpBasic().and().headers().frameOptions().disable(); // For swagger
    return http.build();
}

@Bean
public CorsFilter corsFilter() {
    UrlBasedCorsConfigurationSource source =
            new UrlBasedCorsConfigurationSource();
    CorsConfiguration config = new CorsConfiguration();
    config.setAllowCredentials(true);
    config.addAllowedOriginPattern("*");
    config.addAllowedHeader("*");
    config.addAllowedMethod("*");
    source.registerCorsConfiguration("/**", config);
    return new CorsFilter(source);
}

У меня была аналогичная проблема с Spring Security 6, поскольку она игнорировала мою конфигурацию CORS. Если вы хотите быть по-настоящему уверены, что SecurityConfig подберет вашу пользовательскую конфигурацию CorsConfiguration, вы также можете применить ее с помощью:

      @Override
protected void configure(HttpSecurity http) throws Exception {
   http
       .authorizeRequests()
           ...
       .cors(cors -> cors.configurationSource(yourCustomCorsConfigurationSource))
           ...;
}

Поскольку ни один из этих опубликованных примеров мне не помог, я использовал свои собственные знания. Обычно самые сложные ошибки случаются со мной. Вот как я справился с этой ошибкой.

В этом методе:

      @Bean
CorsConfigurationSource corsConfigurationSource() {
    CorsConfiguration cors = new CorsConfiguration();
    cors.setAllowedMethods(Arrays.asList("POST", "GET", "PUT", "HEAD", "DELETE"));
    UrlBasedCorsConfigurationSource source = new
            UrlBasedCorsConfigurationSource();
    source.registerCorsConfiguration("/**", new CorsConfiguration().applyPermitDefaultValues());
    return source;
}

CorsConfiguration по умолчанию имеет разрешенный метод:POST, HEAD, GET, поэтому PUT, DELETE НЕ БУДУТ РАБОТАТЬ! Я создал новый экземпляр CorsConfiguration и установил разрешенные методы:

      cors.setAllowedMethods(Arrays.asList("POST", "GET", "PUT", "HEAD", "DELETE"));

так что теперь мой метод выглядит так:

          @Bean
    CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration cors = new CorsConfiguration();
        cors.setAllowedMethods(Arrays.asList("POST", "GET", "PUT", "HEAD", "DELETE"));
        UrlBasedCorsConfigurationSource source = new
                UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", cors.applyPermitDefaultValues());
        return source;
    }

и это работает как шарм. Надеюсь, это кому-нибудь поможет. Конечно, все остальные настройки выполняются с помощью документации spring.io.

Попробуй это:

      import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;

import java.util.Arrays;
import java.util.List;

@Component
public class CorsFilterConfig {

    public static final List<String> allowedOrigins = Arrays.asList("*");

    @Bean
    public FilterRegistrationBean<CorsFilter> initCorsFilter() {
        // @formatter:off
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        CorsConfiguration config = new CorsConfiguration();
        config.setAllowCredentials(true);
        config.setAllowedHeaders(Arrays.asList("Authorization", "Cache-Control", "Content-Type"));
        config.addAllowedMethod("*");
        config.setAllowedOrigins(allowedOrigins);
        source.registerCorsConfiguration("/**", config);
        FilterRegistrationBean<CorsFilter> bean = new FilterRegistrationBean<>(new CorsFilter(source));
        bean.setOrder(Ordered.HIGHEST_PRECEDENCE);
        return bean;
        // @formatter:on
    }
}

В моем случае я использую дистрибутив CloudFront для кода пользовательского интерфейса, развернутого на S3. Из S3 запросы кода пользовательского интерфейса передаются на сервер API, и я столкнулся с той же ошибкой в ​​консоли браузера. Это вводило в заблуждение, указывая на проблемы с CORS, но основной причиной была неспособность CloudFront подключиться к исходному серверу. Поэтому для решения этой проблемы важно проверить работоспособность и доступность исходного сервера.

Вы легко можете добавить@CrossOriginаннотация, чтобы разрешить их все, если вы используетеUsernamePasswordAuthenticationFilter. И в настройках безопасностиhttp.cors().and(). Это сработало для меня.

      @CrossOrigin(origins = "*")
public class CustomAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
    private final AuthenticationManager authenticationManager;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        CustomAuthenticationFilter customAuthenticationFilter = new CustomAuthenticationFilter(authenticationManagerBean());
        customAuthenticationFilter.setFilterProcessesUrl("/api/login");
        http
            .csrf().disable();
        http
            .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS);

        // We can ant match out paths to the corresponding roles --> we allow certain roles to access certain API's
        http
            .cors()
                .and(); 
        http
            .authorizeRequests()
                .antMatchers(HttpMethod.POST, "/**").permitAll(); 
...

Это сработало для: spring-boot-starter-parent 2.2.6.RELEASE

@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {

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

Замените "*" на что-нибудь значимое в продукте

Добавьте приведенную ниже конфигурацию в основное приложение. Это сработало у меня в весеннем загрузочном приложении 2.3.1

package com.example.restservicecors;

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;

@SpringBootApplication
public class RestServiceCorsApplication {

    public static void main(String[] args) {
        SpringApplication.run(RestServiceCorsApplication.class, args);
    }

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

}

Справочный источник: https://spring.io/guides/gs/rest-service-cors/

Другой подход, если вы хотите переместить этот код в файл конфигурации.

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**").allowedOrigins("*").allowedHeaders("*").allowedMethods("*");
    }
}
Другие вопросы по тегам