js跨域请求获取不到自定义响应头的问题
近期做一个前后端分离的项目,前端通过跨域请求访问后端,后端的返回结果中有一个自定义的响应头。但是在浏览器中,前端却无法获取到这个响应头:
问题场景
我们尝试分别用jQuery ajax、XMLHttpRequest、fetch三种方式来发送一个 CORS
跨域请求,并打印响应头:
然后在Chrome浏览器的开发者工具中查看这个跨域请求的请求头和响应头:
其中可以看到,前端页面的端口是 8848
,后端服务的端口是 8888
,不同源,所以这是一个 CORS
跨域请求。另外,后端服务返回了一个自定义的响应头:
但是,当我们查看控制台时,却发现只打印了以下内容:
在众多的响应头中,只有 content-type
可以通过js获取到,其他响应头(包括自定义的响应头)都无法获取。
如果我们将前端页面的端口改成 8888
,和后端同源,重新发送一个同源请求,这种情况控制台打印如下:
可见,这三种方式在不跨域时,js都可以获取到完整的响应头(除了 Set-Cookie
)。
原因分析
在测试了Chrome、FireFox、Safari、Edge等多个主流浏览器之后,我们发现 CORS
跨域请求的响应头都无法用js完整获取。因此,应该是浏览器对这一功能做了限制。
由于这些主流浏览器都遵循WHATWG制定的Web标准,因此我们追根溯源,在最新版本的Web请求标准规范中,找到了以下内容:
A CORS-safelisted response-header name, given a CORS-exposed header-name list list, is a header name that is a byte-case-insensitive match for one of
Cache-Control
Content-Language
Content-Length
Content-Type
Expires
Last-Modified
Pragma
- Any value in list that is not a forbidden response-header name.
默认情况下,只有以上7个响应头是CORS安全的(早期有6个,Content-Length
是后面加上的),因此浏览器也只允许js获取这7个响应头。除此之外,规范中还有这样的说明:
A response has an associated CORS-exposed header-name list (a list of zero or more header names). The list is empty unless otherwise specified.
A response will typically get its CORS-exposed header-name list set by extracting header values from the Access-Control-Expose-Headers header. This list is used by a CORS filtered response to determine which headers to expose.
规范表明每个响应都有一个CORS公开响应头列表,这个列表默认为空,服务器可以通过添加 Access-Control-Expose-Headers
响应头来指定哪些响应头是可以公开的。
因此,浏览器在发送 CORS
跨域请求时,js只能获取到7个CORS安全的响应头、以及 Access-Control-Expose-Headers
中指定的响应头。
以上标准在MDN Web文档中也可以查看,相关链接:
解决办法
原因已经找到,我们只需要按照规范文档,在后端添加 Access-Control-Expose-Headers
响应头即可。这个响应头的语法为:
使用通配符 *
可以公开所有响应头,但是并不安全。因此我们可以只把部分响应头公开给前端,多个响应头名称之间用 ,
分隔。
Java中添加响应头的方法:
Nginx中添加响应头的方法:
添加上 Access-Control-Expose-Headers
响应头之后,我们再发送一个 CORS
跨域请求,查看控制台打印的内容:
这样,我们终于能在跨域请求时,用js获取到我们想要的响应头了。