spring配置CORS后未返回Access-Control-Allow-Origin的踩坑解决
Posted Victor _Lv
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了spring配置CORS后未返回Access-Control-Allow-Origin的踩坑解决相关的知识,希望对你有一定的参考价值。
一、配置方式
在 Spring 框架下解决 CORS 问题,前面试了两种方法,发现在一种场景下,HTTP Response header 始终未应答 Access-Control-Allow-Origin:*
(1)第一种方式,通过在 Controller 层增加 @CrossOrigin
注解。
@CrossOrigin
@RestController
@RequestMapping("/file")
public class FileController {
...
}
(2)第二种方式,利用 Spring 的 WebMvcConfigurer 中的 addCorsMappings
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* 支持跨域资源共享-CORS 配置
*/
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
/**
* addMapping: /** 表示所有路径及子路径下的 HTTP 应答都进行 Access-Control 标头包装
* allowedOrigins: response header 中增加 Access-Control-Allow-Origin: * (表示允许所有 Origin 来源的跨域请求)
* allowedMethods:response header 中增加 Access-Control-Allow-Methods: * (表示允许所有 HTTP Method)
* allowedHeaders:response header 中增加 Access-Control-Allow-Headers: *
* maxAge:response header 中增加 Access-Control-Max-Age: 1800 (表示建议浏览器缓存预检【Options请求】结果 1800s,可以降低服务端处理预检请求的压力)
*
* 配置解释参考:https://cloud.tencent.com/developer/article/1513418
*/
registry.addMapping("/**")
.allowedOrigins("*")
.allowedMethods("*")
.allowedHeaders("*")
.maxAge(1800);
}
}
二、问题踩坑
这两种方式测下来发现都有一个问题,就是如果 Origin 和 请求的 Url 地址是同源的( HTTP Method + host + port 完全一致则认为同源),则 Spring 框架并不会在 Response Header 中应答 Access-Control-Allow-Origin: *
,“同源访问时Spring不会返回Access-Control-Allow-Origin标头”,这个下的判断翻了下源码和Google了下还没找到理论依据,只是通过测试下的判断。Tips:翻看源码发现第一种加 @CrossOrigin
注解的方式跟第二种通过WebMvcConfigurer .addCorsMappings()
配置, Spring 内部实现,其实是走的同一套,所以两种方式都会碰到同样的问题。
然后正好我们有个场景,是内部的前端 Ajax 跨域调用到这个服务(假设叫 S1 服务,对应服务地址为 https://hello.com/file
)上来,并且域名用的是一样的都是 hello.com
(假设其他服务请求地址为:hello.com/server
),但是这个域名支持 https
和 http
两种方式访问。结果发现,用 https
在 Origin: https://hello.com/server
的 Origin header 下访问https://hello.com/file
是会返回 Access-Control-Allow-Origin: *
,但是换成 http
下去请求 https://hello.com/file
就出问题了,并没有返回 Access-Control-Allow-Origin: *
,导致前端Ajax请求被浏览器因 CORS 问题而挡掉。
三、原因分析
按照刚才的判断: “同源访问时Spring不会返回Access-Control-Allow-Origin标头”,理论上Origin: https://hello.com/server
下请求 https://hello.com/file
是同源,不会出现 Access-Control-Allow-Origin: *
,但偏偏刚好相反,origin = http & url = https
这个搭配出不来 Access-Control
,origin = https & url = https
这个搭配却出来了 Access-Control
。
后来回想了下,也注意到了 Response 内容里的 Server: nginx/1.13.5
,突然想起来 nginx 这类外部网关,会把外部进来的 https 请求解密解包,然后以 http 的方式转发给内网服务。
这就解释通了上面的相反现象,origin = http & url = https
这种搭配下,因为 url 中的 https 经过 nginx 处理成 http 了,到了 Spring 层实际是个 http 请求,所以 Spring 判断其实是同源,于是没有应答 Access-Control-Allow-Origin: *
;而origin = https & url = https
这种搭配,恰恰因为 nginx 处理成 http,到了 Spring 层判断时发现 origin 的是 https 地址,而 url 是 http,不同源,所以才有Access-Control
。
当然,以上坑也只有在内部同域名下Ajax访问才有出现。提供给到外部服务时,一般两边域名就不一样,无论是 https / http 与否,Spring 肯定都会判断是 cross origin,所以都会有Access-Control
。
四、解决方案
解决方案很简单,就是换一种方式,能够强制给 response header 加上Access-Control-Allow-Origin: *
,不管 同源还是 Cross Origin 与否。
可以自己在 Controller 方法上手工给 HttpServletResponse
增加 header:
response.setHeader("Access-Control-Allow-Origin", "*");
这种办法很傻很原始,而且每个方法都得写一遍,更关键的是,针对 OPTIONS
预检请求(浏览器针对跨域请求在 POST 之前一般会先自动发 OPTIONS 预检请求,详细解释见 CORS跨域资源共享(一):模拟跨域请求以及结果分析,理解同源策略)也得加个方法处理 response,否则OPTIONS
请求就会因为跨域而被 Block 掉。
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebFilter(filterName = "CorsFilter")
@Configuration
public class CorsFilter implements Filter {
@Override
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-Credentials", "true");
response.setHeader("Access-Control-Allow-Methods", "POST, GET, PATCH, DELETE, PUT");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
chain.doFilter(req, res);
}
参考文献:
以上是关于spring配置CORS后未返回Access-Control-Allow-Origin的踩坑解决的主要内容,如果未能解决你的问题,请参考以下文章
spring配置CORS后未返回Access-Control-Allow-Origin的踩坑解决
spring配置CORS后未返回Access-Control-Allow-Origin的踩坑解决