背景:后端服务中明明已经配置了跨域,但是一直未生效,最后发现是因为不同的HTTP版本对Header头的要求不同导致的。
一、背景:
我们有个需求:将APP端的所有功能复制一份到PC端。为了区分流量到底是来自APP还是PC。
因此让前端小伙伴在HTTP Header头加上 clientSource
字段,来自APP端的流量则clientSource=app,否则clientSource=pc。
然后后端利用AOP技术 编写切面(包含切点、通知)。
在切面中打印日志的地方加上clientSource的取值,这样当某个流量触发后端导致异常时,我们可以根据LOG日志快速定位出流量来源。
因此,第一版Cors如下配置:
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest servletRequest = (HttpServletRequest) request;
HttpServletResponse servletResponse = (HttpServletResponse) response;
/**
* Origin 头设置
*/
String origin = servletRequest.getHeader("Origin");
if (StringUtils.isBlank(origin)) {
servletResponse.setHeader("Access-Control-Allow-Origin", "*");
} else {
servletResponse.setHeader("Access-Control-Allow-Origin", origin);
}
/**
* Header 头设置
*/
StringBuffer allowHeaders = new StringBuffer("clientSource,");
Enumeration<String> headerNames = servletRequest.getHeaderNames();
while (headerNames.hasMoreElements()) {
allowHeaders.append(headerNames.nextElement()).append(",");
}
servletResponse.setHeader("Access-Control-Allow-Headers", allowHeaders.deleteCharAt(allowHeaders.length() - 1).toString());
/**
* Methods 头设置
*/
servletResponse.setHeader("Access-Control-Allow-Methods", "GTP,POST,PUT,DELETE,OPTIONS");
/**
* 开启Cookie每次传输
*/
servletResponse.setHeader("Access-Control-Allow-Credentials", "true");
if (OPTIONS.equalsIgnoreCase(servletRequest.getMethod())) {
return;
}
chain.doFilter(request, response);
}
现象:前端按约定添加了clientSource
header,但是浏览器Network现实,每一次的OPTIONS(所有带有自定义Header与非GET请求 两者满足其一都会发送OPTIONS预发请求)都无法通过,导致跨域了。
二、解决办法
摘自HTTP2规范:
具体见:RFC 7540 - Hypertext Transfer Protocol Version 2 (HTTP/2)
8.1.2\. HTTP Header Fields
HTTP header fields carry information as a series of key-value pairs.
For a listing of registered HTTP headers, see the "Message Header
Field" registry maintained at <https://www.iana.org/assignments/
message-headers>.
Just as in HTTP/1.x, header field names are strings of ASCII
characters that are compared in a case-insensitive fashion. However,
header field names MUST be converted to lowercase prior to their
encoding in HTTP/2\. A request or response containing uppercase
header field names MUST be treated as malformed (Section 8.1.2.6).
上面的意思大致是:
与HTTP1.x相同,Header是ASCII码,但是HTTP2的Header Name必须是全小写的。因此将上面的文章修改成如下即可:
/**
* Header 头设置
*/
StringBuffer allowHeaders = new StringBuffer("clientSource,clientsource");
Enumeration<String> headerNames = servletRequest.getHeaderNames();
while (headerNames.hasMoreElements()) {
allowHeaders.append(headerNames.nextElement()).append(",");
}
servletResponse.setHeader("Access-Control-Allow-Headers", allowHeaders.deleteCharAt(allowHeaders.length() - 1).toString());
三、疑问
你可能会有将Access-Control-Allow-Headers
设置为“*”,这确实是一个不错的办法,可以非常方便的解决了HTTP不同版本Header的兼容性问题。
但是这里设置的servletResponse.setHeader("Access-Control-Allow-Credentials", "true");
导致所有的配置无法使用“”,同样,其他的几个配置也都无法使用“”;
具体见:
DevDocs-access-control-allow-origin的说明
对于Access-Control-Allow-Credentials
请移步这: