问题

使用Vue对后台的资源进行访问,发生跨域的请求,会产生如下的报错

Access to fetch at ‘http://localhost:8080/hdfs/read-csv?path=/output/hdfsJobByMonth/part-r-00000‘ from origin ‘http://localhost:9528‘ has been blocked by CORS policy: No ‘Access-Control-Allow-Origin’ header is present on the requested resource. If an opaque response serves your needs, set the request’s mode to ‘no-cors’ to fetch the resource with CORS disabled.

这个错误表明前端应用(运行在 http://localhost:9528)试图通过 fetch API 访问后端服务(运行在 http://localhost:8080)的某个资源,但是由于跨域资源共享(CORS)策略的限制,这个请求被浏览器阻止了

CORS 策略要求服务器在响应中包含一些特定的 HTTP 头信息,比如 Access-Control-Allow-Origin,来告诉浏览器哪些源(即哪些域、协议和端口)有权访问该资源。在这个例子中,服务器没有在响应中包含这个必要的头信息。

要解决这个问题,有几种解决办法:

  1. 配置后端服务以支持 CORS
    在后端服务(http://localhost:8080)上添加 CORS 支持。这通常意味着在响应中包含适当的 HTTP 头,如 Access-Control-Allow-Origin: *(允许所有源)或 Access-Control-Allow-Origin: http://localhost:9528(仅允许来自 http://localhost:9528 的请求)。但是请注意,将 Access-Control-Allow-Origin 设置为 * 可能会带来安全风险,特别是如果服务涉及敏感数据。

  2. 使用代理服务器
    可以在前端应用中设置一个代理服务器,该服务器将请求转发到后端服务,并在转发响应时添加 CORS 头。这通常可以通过配置开发服务器(如 webpack-dev-server、Create React App 的开发服务器等)来实现。

  3. 使用 no-cors 模式(不推荐):
    可以将 fetch 请求的模式设置为 no-cors 来绕过 CORS 策略,但这将导致响应变得不透明(opaque),前端无法读取响应的内容。因此,这通常不是一个好的解决方案,除非确实不需要读取响应的内容。

  4. 在本地开发时禁用浏览器的 CORS 检查
    这可以通过在浏览器中使用插件或特殊模式来实现,但请注意,这只是一个临时的解决方案,并且不应该在生产环境中使用,因为它会暴露应用于安全风险。

  5. 使用 JSONP(如果适用):
    如果后端服务支持 JSONP,并且只是想获取 JSON 数据,那么可以使用 JSONP 来绕过 CORS 策略。但是,JSONP 有一些限制,比如它只支持 GET 请求,并且只能返回 JSONP 格式的响应。

解决

使用第一种方式,在后端服务配置允许跨域访问
如下有几种方式:

方法一:全局CORS配置

可以通过配置类中添加一个WebMvcConfigurer bean来全局配置CORS。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebConfig implements WebMvcConfigurer {

@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**") // 允许所有路径
.allowedOrigins("http://localhost:9528") // 允许哪些源的请求
.allowedMethods("GET", "POST", "PUT", "DELETE") // 允许哪些方法
.allowedHeaders("*") // 允许哪些请求头
.allowCredentials(true) // 是否允许携带认证信息(cookies, HTTP认证及客户端SSL证明等)
.maxAge(3600); // 缓存CORS信息的时间(秒),默认是1800秒
}
}

方法二:使用@CrossOrigin注解

也可以在Controller类或者特定的方法上使用@CrossOrigin注解来启用CORS。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@CrossOrigin(origins = "http://localhost:9528") // 允许哪些源的请求
public class MyController {

@GetMapping("/hdfs/read-csv")
public String readCsv(@RequestParam String path) {
// ... 逻辑代码 ...
return "Data from CSV";
}
}

方法三:自定义CORS过滤器

需要更复杂的CORS配置,可以创建一个自定义的CORS过滤器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;

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

@Component
public class CustomCorsFilter implements Filter, Ordered {

@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:9528");
response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "x-requested-with");
chain.doFilter(req, res);
}

@Override
public int getOrder() {
return 0; // 设置过滤器顺序
}
}

注意事项

  • 当使用allowedOrigins时,如果只想允许来自特定域名的请求,就指定那个域名。如果想允许来自任何域名的请求(注意:这可能会带来安全风险),可以使用"*"
  • allowCredentials(true)允许跨域请求携带认证信息(如cookies),但这要求Access-Control-Allow-Origin不能是"*",而必须是一个明确的域名。
  • 如果前端应用需要处理预检请求(OPTIONS请求),确保后端配置也支持这些请求。预检请求是浏览器在发送实际请求之前发送的,以检查服务器是否允许跨域请求。