背景:后端服務(wù)中明明已經(jīng)配置了跨域,但是一直未生效,最后發(fā)現(xiàn)是因?yàn)椴煌腍TTP版本對(duì)Header頭的要求不同導(dǎo)致的。
一、背景:
我們有個(gè)需求:將APP端的所有功能復(fù)制一份到PC端。為了區(qū)分流量到底是來自APP還是PC。
因此讓前端小伙伴在HTTP Header頭加上 clientSource 字段,來自APP端的流量則clientSource=app,否則clientSource=pc。
然后后端利用AOP技術(shù) 編寫切面(包含切點(diǎn)、通知)。
在切面中打印日志的地方加上clientSource的取值,這樣當(dāng)某個(gè)流量觸發(fā)后端導(dǎo)致異常時(shí),我們可以根據(jù)LOG日志快速定位出流量來源。
因此,第一版Cors如下配置:
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest servletRequest = (HttpServletRequest) request;
HttpServletResponse servletResponse = (HttpServletResponse) response;
/**
* Origin 頭設(shè)置
*/
String origin = servletRequest.getHeader("Origin");
if (StringUtils.isBlank(origin)) {
servletResponse.setHeader("Access-Control-Allow-Origin", "*");
} else {
servletResponse.setHeader("Access-Control-Allow-Origin", origin);
}
/**
* Header 頭設(shè)置
*/
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 頭設(shè)置
*/
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);
}
現(xiàn)象:前端按約定添加了clientSourceheader,但是瀏覽器Network現(xiàn)實(shí),每一次的OPTIONS(所有帶有自定義Header與非GET請(qǐng)求 兩者滿足其一都會(huì)發(fā)送OPTIONS預(yù)發(fā)請(qǐng)求)都無法通過,導(dǎo)致跨域了。
二、解決辦法
摘自HTTP2規(guī)范:
具體見: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 頭設(shè)置
*/
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());
三、疑問
你可能會(huì)有將Access-Control-Allow-Headers設(shè)置為“*”,這確實(shí)是一個(gè)不錯(cuò)的辦法,可以非常方便的解決了HTTP不同版本Header的兼容性問題。
但是這里設(shè)置的servletResponse.setHeader("Access-Control-Allow-Credentials", "true");導(dǎo)致所有的配置無法使用“”,同樣,其他的幾個(gè)配置也都無法使用“”;
具體見:
DevDocs-access-control-allow-origin的說明
對(duì)于Access-Control-Allow-Credentials請(qǐng)移步這: