跨域问题踩坑记录——附多种报错详细解决方案

跨域是出于浏览器的同源策略限制。同源策略(Sameoriginpolicy)是一种约定,它是浏览器最核心也最基本的安全功能,当一个请求url的协议、域名、端口三者之间任意一个与当前页面url不同即为跨域。

同源策略是浏览器的一个安全功能,不同源的客户端脚本在没有明确授权的情况下,不能读写对方资源

场景:

前端调用接口接收文件流下载文件,以本地环境为例,系统域名为http://localhost:8090,接口域名为'http://127.0.0.1:8080,后端使用springboot

此时浏览器控制台报错:

Access to XMLHttpRequest at 'http://127.0.0.1:8080/zyfbpt/dbreport/export?domainKey=zyfzyfbpt' from origin 'http://localhost:8090' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.

尝试一:接口返回文件流时响应头设置Access-Control-Allow-Origin

String origin = request.getHeader("Origin");
response.addHeader("Access-Control-Allow-Origin", origin);

重启服务后发现并没有生效,报错内容不变(有没有大佬帮忙分析下这样为什么不生效)

尝试二:使用@CrossOrigin注解

//1.在controller类上使用
@Controller
@CrossOrigin
public class MyController {

}
//2.在方法上使用
@CrossOrigin
@RequestMapping(value = "/test",method = RequestMethod.POST)
public void test(@RequestBody TestRequest testRequest){
    
}

直接加上后发现依然不生效,搜索后总结原因有以下几点:

情况一:在方法上使用@CrossOrigin注解,但@RequestMapping注解中没有指定Get、Post方式,通过method = RequestMethod.POST/GET指定或者指定@CrossOrigin注解中的methods = {RequestMethod.POST}后,问题解决。

//在@CrossOrigin中methods指定方式
@CrossOrigin(methods = {RequestMethod.POST})

//或者@RequestMapping注解定义method
@RequestMapping(value = "/test",method = {RequestMethod.POST})

情况二:跨域访问响应状态码是405-Method Not Allowed,请求行中指定的请求方法不能被用于请求相应的资源。这个是请求方式不正确,检查代码。

情况三:查看springboot版本,如果是2.0以后版本,allowCredentials属性的默认值为false,返回的响应头AccessControlAllowCredentials属性值也为false,如果客户端携带cookie的请求这时是不能跨域访问的,所以需要手动在注解中设置allowCredentials为true

例如:前端ajax请求参数withCredentials=true,表示请求带上cookie信息

//a标签下载文件
axios({
      method: 'POST',
      url: downloadUrl,
      data: params,
      withCredentials:true,
      responseType: 'blob'
    }).then(res => {
      let url = window.URL.createObjectURL(new Blob([res.data]));
      let link = document.createElement("a");
      link.style.display = "none";
      link.href = url;
      link.setAttribute("download", `report-${Date.now()}.docx`);
      document.body.appendChild(link);
      link.click();
});

此时就需要后端设置allowCredentials=true解决跨域,然而这里如下配置后再请求报错了:

@CrossOrigin(allowCredentials = "true")
@RequestMapping(value = "/dbreport/export", method = RequestMethod.POST)
@ResponseBody
public Object export(@RequestBody JSONObject json HttpServletResponse response) {

}

后端报错:

java.lang.IllegalArgumentException: When allowCredentials is true, allowedOrigins cannot contain the special value "*" since that cannot be set on the "Access-Control-Allow-Origin" response header. To allow credentials to a set of origins, list them explicitly or consider using "allowedOriginPatterns" instead.

翻译过来就是:

当allowCredentials为真时,allowedorigin不能包含特殊值"*",因为不能在Access-Control-Allow-Origin响应头中设置该值。要允许凭证到一组origins,显示地列出它们,或者考虑使用"allowedOriginPatterns”代替。

解决:

按提示加上originPatterns = "*"后问题解决

@CrossOrigin( originPatterns = "*", allowCredentials = "true", allowedHeaders = "*")
@RequestMapping(value = "/dbreport/export", method = RequestMethod.POST)
@ResponseBody
public Object export(@RequestBody JSONObject json HttpServletResponse response) {

}

尝试三:全局CORS配置

配置后无需每个controller或方法上加@Configuration注解了

@Configuration
public class SpringMvcConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
            // 放行哪些域名,可以多个
            .allowedOriginPatterns("*")  
            // 是否发送Cookie信息                       
            .allowCredentials(true)
            // 允许的模式
            .allowedOriginPatterns("*");
            // 放行哪些请求方式                      
            .allowedMethods("GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS", "PATCH") 
            // 放行哪些原始域(头部信息)
            .allowedHeaders("*")
            // 暴露哪些头部信息(因为跨域访问默认不能获取全部头部信息)                             
            .exposedHeaders("Header1", "Header2") 
            // 预请求的结果有效期,默认1800分钟,3600是一小时
            .maxAge(3600);                                      
    }
}

搜索后发现有网友说此种配置有以下情况,但是我自己的接口就是用HttpServletResponse返回的文件流,没有加下边的响应头也成功了,供参考

适用情况:

1返回字符串或对象的接口

2.通过ResponseEntity提供文件下载功能的接口

不适用的情况:

直接适用HttpServletResponse进行内容返回的接口,需要在响应头加上下边内容

response.addHeader("Access-Control-Allow-Origin", "*");
response.addHeader("Access-Control-Allow-Methods", "POST,GET,OPTIONS,DELETE");
response.addHeader("Access-Control-Max-Age", "9600");
response.addHeader("Access-Control-Allow-Headers", "x-requested-with");

解决问题途中还遇到了下边的错误:

错误一:

此时前端请求带withCredentials=true,携带cookie请求

后端配置为:

@Configuration
public class SpringMvcConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**");                                
    }
}
Access to XMLHttpRequest at 'http://127.0.0.1:8080/zyfbpt/dbreport/export?domainKey=zyfzyfbpt' from origin 'http://localhost:8090' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: 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'. The credentials mode of requests initiated by the XMLHttpRequest is controlled by the withCredentials attribute.

原因:如果跨域请求设置了withCredentials:true, 浏览器发起请求的时候会携带上所在域的cookie信息,同时跨域标准规定请求响应header中Access-Control-Allow-Origin不能设置*,必须设置为具体的跨域域名。

解决方法一:

前端去掉withCredentials=true参数

解决方法二:

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

错误二:

Access to XMLHttpRequest at 'http://127.0.0.1:8080/zyfbpt/dbreport/export?domainKey=zyfzyfbpt' from origin 'http://localhost:8090' has been blocked by CORS policy: The 'Access-Control-Allow-Origin' header contains multiple values '*, http://localhost:8090', but only one is allowed.

原因:Access-Control-Allow-Origin响应header重复添加了导致报错,一般出现该原因是因为运维 和 接口方同时添加了Access-Control-Allow-Origin的响应header。

解决:检查是否多处配置Access-Control-Allow-Origin,如CROS全局配置、contraller中的@CrossOrigin注解、代码中设置Access-Control-Allow-Origin响应头、nginx等网关层添加等,

只保留一处即可


版权声明:本文为qq_35751333原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
THE END
< <上一篇
下一篇>>