Spring Boot Data Rest + CORS未正确启用OPTIONS/DELETE请求

4

我有一个非常简单的例子,但我无法让它工作。

我有一个模拟数据库的域和我的存储库。

public interface MyTestRepository extends CrudRepository<MyTest, Integer> {
}

我使用http://resttesttest.com/进行测试。GET方法返回JSON REST信息没有问题。
我可以查询端点http://localhost:8080/mytest/1,并从数据库中获取id = 1的信息。
然而,当我尝试使用DELETE选项时出现问题。如果我在http://localhost:8080/mytest/1上运行DELETE,则会得到以下错误:
响应预检请求未通过访问控制检查:所请求的资源上没有“Access-Control-Allow-Origin”标头。因此,源'http://resttesttest.com'被拒绝访问。该响应的HTTP状态码为403。
我最初尝试了下面的方法,但发现不能使用它,因为我使用的是Spring-data-Rest。https://jira.spring.io/browse/DATAREST-573
@Override
public void addCorsMappings(CorsRegistry registry) {
    registry.addMapping("/**")
        .allowedOrigins("*")
        .allowedMethods("*")
        .allowedHeaders("*")
        .allowCredentials(true).maxAge(3600);
}

我在谷歌上搜索到了这篇文章。 如何在Spring Boot + Spring Security应用程序中配置CORS? 于是我添加了:
@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(0);
    return bean;
}

我还找到了这个帖子:

Spring Data Rest和Cors

我也尝试过下面的代码,但没有成功。

@Bean
public FilterRegistrationBean corsFilter() {
    UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
    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);
    final FilterRegistrationBean bean = new FilterRegistrationBean(new CorsFilter(source));
    bean.setOrder(0);
    return bean;
}

我在测试中添加了一个“catch all”来允许所有的CORS通过,但是即使我使用了“*”,我仍然收到“No 'Access-Control-Allow-Origin'”错误。

此时,我不知道我缺少什么,导致预检请求无法通过访问控制检查。

curl在发出删除请求时没有任何问题。

编辑:

最终找到了确切的解决方法。我不确定我现有的方法和这种方法之间的区别,但这似乎有效。

import org.springframework.stereotype.Component;

import javax.servlet.*;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * Note this is a very simple CORS filter that is wide open.
 * This would need to be locked down.
 * Source: https://dev59.com/XJrga4cB1Zd3GeqPtd8W
 */
@Component
public class CORSFilter implements Filter {

    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
        HttpServletResponse response = (HttpServletResponse) res;
        response.setHeader("Access-Control-Allow-Origin", "*");
        response.setHeader("Access-Control-Allow-Methods", "POST, GET, PUT, OPTIONS, DELETE");
        response.setHeader("Access-Control-Max-Age", "3600");
        response.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
        chain.doFilter(req, res);
    }

    public void init(FilterConfig filterConfig) {}

    public void destroy() {}

}

在不使用spring-boot-starter-security的情况下,使用Spring Boot MVC/Rest时,我必须使用上述CORSFIlter来允许预检测的OPTIONS请求正常工作。如果没有过滤器,当我使用Postman发送带有Origin头的OPTIONS请求时,会收到403 Forbidden的响应(如果我不带Origin头,则OPTIONS请求可以正常工作)。为了使OPTIONS请求正常工作,我需要同时使用“public class CORSFilter implements Filter”和“public class CORSconfiguration implements WebMvcConfigurer”。 - LeslieM
4个回答

3
下面的配置在基于Spring Data Rest的应用程序中适用于我。需要注意的重要一点是,该过滤器被注册以在安全过滤器链启动之前执行。
@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter
{
  @Override
  public void configure(HttpSecurity http) throws Exception
  {
    http.addFilterBefore(corsFilter(), ChannelProcessingFilter.class);
  }

  @Bean
  protected Filter corsFilter()
  {
    UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();

    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");
    config.addExposedHeader("Location");

    source.registerCorsConfiguration("/**", config);

    return new CorsFilter(source);
  }
}

谢谢,但那似乎也不起作用。我将其直接复制粘贴到我的Spring应用程序中,禁用了所有其他@Configuration类,添加了导入,仍然得到相同的错误。您是否正在使用Springs Boot Data Rest?如果是,您是否正在使用自动生成REST端点的<model>Repository类? - Kevin Vasko

2
这是我使用的允许所有CORS的servlet过滤器:
public class PermissiveCORSFilter implements Filter {

    private static final Logger LOGGER = LoggerFactory.getLogger(PermissiveCORSFilter.class);
    private static final Pattern PATTERN = Pattern.compile("^[a-zA-Z0-9 ,-_]*$");

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

        String origin;
        String credentialFlag;
        if (request.getHeader("Origin") == null) {
            origin = "*";
            credentialFlag = "false";
         } else {
            origin = request.getHeader("Origin");
            credentialFlag = "true";
         }

        // need to do origin.toString() to avoid findbugs error about response splitting        
        response.addHeader("Access-Control-Allow-Origin", origin.toString());
        response.setHeader("Access-Control-Allow-Credentials", credentialFlag);
        if ("OPTIONS".equals(request.getMethod())) {
            LOGGER.info("Received OPTIONS request from origin:" + request.getHeader("Origin"));
            response.setHeader("Access-Control-Allow-Methods", "GET,POST,HEAD,OPTIONS,PUT,DELETE");
            response.setHeader("Access-Control-Max-Age", "3600");
            String headers = StringUtils.trimToEmpty(request.getHeader("Access-Control-Request-Headers"));
            if (!PATTERN.matcher(headers).matches()) {
                throw new ServletException("Invalid value provided for 'Access-Control-Request-Headers' header");
            }
            response.setHeader("Access-Control-Allow-Headers", headers); // allow any headers
        }
        chain.doFilter(req, res);
    }

    @Override
    public void init(FilterConfig filterConfig) {
        // Do nothing
    }

    @Override
    public void destroy() {
        // Do nothing
    }

谢谢,但基于我在原始帖子中提到的那个错误,这对我不起作用,因为我正在使用Spring Data REST。 - Kevin Vasko
好的,这很有趣。我们正在使用Spring MVC + Spring Data,当从我们的Angular Web客户端调用该过滤器时,它似乎运行良好。 - Pete
你正在使用自动生成的仓库吗?例如:<model>Repository() {}?我标记了@CrossOrigin(origins = "*")的类进行测试时,似乎没有遇到这个问题。 - Kevin Vasko
好的,我改正了。我尝试使用你的代码,但好像不起作用。最终我找到了这个https://dev59.com/XJrga4cB1Zd3GeqPtd8W#39573047,将那段代码复制粘贴到一个类中,它就可以工作了。这与你的概念相同。我会将你的答案标记为正确答案,但我会在我的问题中添加一个编辑,其中包含适用于我的完整代码(请随意将其添加到您的答案中)。然而,我仍然不理解三种不同CORS过滤器之间的区别。 - Kevin Vasko
谢谢 :) 是的,看起来很相似,可能与“Access-Control-Allow-Methods”有关,需要显式允许DELETE。 - Pete

0

我似乎遇到了同样的问题。CrossOrigin配置在GET/PUT/POST方面运行良好,但是当我请求OPTIONS方法来访问我的Spring PostMapping方法时,响应中缺少Access-Control-Allow-Methods头部:

@CrossOrigin
public class ArticleController {

@DeleteMapping("/{uuid}")
public void delete(@PathVariable String uuid) throws ArticleNotFoundException {
    articleService.delete(uuid);
}

如果我使用curl进行DELETE操作,我会得到一个HTTP 200响应,其中包括Access-Control-Allow-Methods:。
$ curl -v -H "Access-Control-Request-Method: DELETE" -H "Origin: http://localhost:4200" -X OPTIONS http://localhost:8080/article/someuuid
< HTTP/1.1 200
< Access-Control-Allow-Origin: http://localhost:4200
< Access-Control-Allow-Methods: PUT,POST,GET,DELETE,OPTIONS
< Allow: GET, HEAD, POST, PUT, DELETE, OPTIONS, PATCH

如果我使用curl进行OPTIONS请求,会得到403错误:
$ curl -v -H "Access-Control-Request-Method: OPTIONS" -H "Origin: http://localhost:4200" -X OPTIONS http://localhost:8080/article/someuuid
< HTTP/1.1 403

我是不是漏掉了什么?

编辑1:

如果我按照使用Spring框架启用OPTIONS请求的CORS添加了该映射到控制器中:

@RequestMapping(
        value = "/**",
        method = RequestMethod.OPTIONS
)
public ResponseEntity handle() {
    return new ResponseEntity(HttpStatus.OK);
}

这将导致:

$ curl -v -H "Access-Control-Request-Method: OPTIONS" -H "Origin: http://localhost:4200" -X OPTIONS http://localhost:8080/article/someuuid
< HTTP/1.1 200
< Access-Control-Allow-Origin: *
< Access-Control-Allow-Methods: OPTIONS
< Allow: GET, HEAD, POST, PUT, DELETE, OPTIONS, PATCH

但这并没有解决Angular仍然会出现403的问题

编辑2: 我已经通过以下控制器代码解决了这个问题:

@RequestMapping("/article")
@CrossOrigin(origins="http://localhost:4200",
    methods = {RequestMethod.PUT, RequestMethod.POST, RequestMethod.GET, RequestMethod.DELETE, RequestMethod.OPTIONS}
    )
public class ArticleController {

@RequestMapping(
        value = "/{uuid}",
        method = { RequestMethod.DELETE })
public void delete(@PathVariable String uuid) throws ArticleNotFoundException {
    articleService.delete(uuid);
}

@RequestMapping(method = { RequestMethod.OPTIONS})
public ResponseEntity handle() {
    return new ResponseEntity(HttpStatus.OK);
}

0

使用Spring Boot 2.2.6

我不得不添加一个过滤器来允许OPTIONS工作。如果没有它,我会得到403 Forbidden的错误。 "Origin"请求头触发了403错误 - 我在Postman中进行了测试,没有发送该头部时OPTIONS可以正常工作。

import javax.servlet.*;
import javax.servlet.http.HttpServletResponse;
@Component
            
        public class CORSFilter implements Filter {
                
                public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
                    HttpServletResponse response = (HttpServletResponse) res;
                    response.setHeader("Access-Control-Allow-Origin", "*");
                    response.setHeader("Access-Control-Allow-Methods", "OPTIONS");  // "POST, GET, PUT, OPTIONS, DELETE"
                    response.setHeader("Access-Control-Max-Age", "3600");
                    response.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
                    chain.doFilter(req, res);
                }
        
            public void init(FilterConfig filterConfig) {}
        
            public void destroy() {}
        
        }

随着

@Configuration
public class ConfigCORS implements WebMvcConfigurer {
@Override
    public void addCorsMappings(CorsRegistry registry) {

        registry.addMapping("/**")  
                .allowedOrigins("*")  allowedOrigins("http://localhost:3000")
                .allowedMethods("POST", "PUT", "GET",  "DELETE", "OPTIONS") 
                .allowedHeaders("Content-Type", "Origin")
                .exposedHeaders("X-Total-Count", "Location", "Access-Control-Allow-Origin")  
                .allowCredentials(false)
                .maxAge(6000);
    }
}

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