CORS问题 - 请求的资源上没有'Access-Control-Allow-Origin'头部

25

我创建了两个Web应用程序——客户端和服务端应用程序。
当它们部署在同一个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 服务器进行部署。

11个回答

23

CORS的预检请求使用不带凭据的HTTP选项,参见跨源资源共享

否则,请发起预检请求。使用方法OPTIONS,从源源头来的请求URL、以覆盖引荐源并设置手动重定向标志和阻止cookie标志,以及以下附加约束条件:

  • 包括一个Access-Control-Request-Method头,头字段值是请求方法(即使这是一个简单的方法)。
  • 如果作者请求头不为空,则包括一个Access-Control-Request-Headers头,头字段值是作者请求头中按字典顺序转换为ASCII小写的逗号分隔的头字段名称列表(即使其中一个或多个是简单标头)。
  • 排除作者请求头。
  • 排除用户凭据。
  • 排除请求主体实体。

您必须允许HTTP选项进行匿名访问。

Spring Security 3

您修改后(并简化)的代码:

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

你仍然需要你的CORS配置(可能需要一些附加值):

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

Spring Security 4

自从 Spring Security 4.2.0 版本开始,你可以使用内置支持,详见 Spring Security 参考文档:

19. CORS

Spring Framework 提供了对 CORS 的一流支持。CORS 必须在 Spring Security 之前处理,因为预检请求不会包含任何 cookies(例如 JSESSIONID)。如果请求不包含任何 cookies 并且 Spring Security 排在首位,则请求将被视为未经认证的用户并被拒绝。

确保先处理 CORS 最简单的方法是使用 CorsFilter。用户可以通过提供 CorsConfigurationSource 来将 CorsFilter 集成到 Spring Security 中,具体见下:

@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 5/6

请查看Spring Security参考文档了解Spring Security 5/6的信息:

CORS

Spring Framework提供了CORS的一流支持。由于预检请求不包含任何cookie(例如JSESSIONID),所以必须在Spring Security之前处理CORS。如果请求不包含任何cookie并且Spring Security排在第一位,则请求会认为用户未经过身份验证(因为请求中没有cookie)并拒绝它。

确保首先处理CORS的最简单方法是使用CorsFilter。用户可以通过提供使用以下内容的CorsConfigurationSourceCorsFilter与Spring Security集成:

@Configuration
@EnableWebSecurity
public class WebSecurityConfig {

  @Bean
  public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
      http
          // by default uses a Bean by the name of corsConfigurationSource
          .cors(withDefaults())
          ...
      return http.build();
  }

  @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 5/6,您不需要两次配置CORS(Spring Security和Spring MVC):

如果您使用Spring MVC的CORS支持,则可以省略指定CorsConfigurationSource,Spring Security将使用提供给Spring MVC的CORS配置:


8
尝试使用这个解决方案仍然出现CORS错误:[...]由于CORS策略已被阻止:预检请求的响应未通过访问控制检查:请求的资源上不存在“Access-Control-Allow-Origin”头。 - Vortilion
1
这会抛出类型不匹配的错误:> 类型不匹配:无法从UrlBasedCorsConfigurationSource转换为CorsConfigurationSource。 - VITTHAL BHANDARI
@ThomasRokicki 你使用的是哪个版本的Spring Security?请参考 https://docs.spring.io/spring-security/site/docs/current/api/org/springframework/security/config/Customizer.html#withDefaults() - dur
@dur spring-boot-starter-parent=3.1.2,spring-boot-starter-security=3.1.2,所以使用的是Spring Security 6.1.2,并且使用了spring-security-oauth2-authorization-server=1.1.1。但是withDefaults()方法未定义 :/ - Thomas Rokicki
1
好的,所以我需要导入静态方法:import static org.springframework.security.config.Customizer.withDefaults; - Thomas Rokicki

18

自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


对我来说可行,但是我必须使用http.csrf().disable(),因为在未启用时iOS和Android应用程序会被阻止。现在WEB和移动端都可以正常工作了。 - Markus G.
谢谢!这解决了我的问题!特别是我漏掉了添加configuration.setAllowedHeaders(ImmutableList.of("Authorization", "Cache-Control", "Content-Type"));。 - Abraham Ciokler
对于Spring 5+,请使用implements WebMvcConfigurer替换extends WebMvcConfigurerAdapter(请参见https://www.baeldung.com/web-mvc-configurer-adapter-deprecated以获取说明)。此外,我使用了`Arrays.asList`而不是`ImmutableList`。 - ponder275
1
为什么我们有相同的第二个配置时,第一个配置是必要的? - GalAbra

1

由于这些发布的例子都没有帮助到我,所以我根据自己的经验进行了处理。

在这种方法中:

@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 允许的方法有:POSTHEADGET,因此 PUTDELETE 将无法正常工作。我创建了一个新的 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文档完成的。

1
在主应用程序中添加以下配置。这在spring boot应用程序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/

1

我在使用Spring Security 6时遇到了类似的问题,它无视了我的CORS配置。 如果你想确保SecurityConfig真正地捕获自定义CorsConfiguration,你也可以强制执行以下操作:

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

我也试过这个,但还是出现了同样的错误。 - Anmol Shah

1
如果你正在使用Angular和Spring Boot,这是适合我的配置。
@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);
}

谢谢!我尝试了很多其他的选项来禁用它,而你的方法是唯一一个对我有效的。 - dvaltrick

0

如果您使用UsernamePasswordAuthenticationFilter,您可以轻松地添加@CrossOrigin注释以允许它们全部。在安全配置中使用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(); 
...

0
在我的情况下,我正在使用一个CloudFront分发来部署在S3上的UI代码。从S3中,UI代码的请求会发送到API服务器,而我在浏览器控制台遇到了相同的错误。这个错误有些误导性,它表明存在CORS问题,但根本原因是CloudFront无法连接到源服务器。因此,检查源服务器的健康状况和可达性是解决这个问题的关键。

0
在我的情况下,我有启用OAuth安全性的资源服务器,但上述任何解决方案都没有起作用。经过一些调试和搜索,我找到了原因。
@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依赖项添加了不同类型的过滤器,因此我们可能会出现基于顺序的问题。


0

试试这个:

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
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接