Access-Control-Allow-Origin 与 WithCredentials
遇到的问题
在我们一个商城项目当中,前后端分离,前端使用Ajax(XMLHttpRequest),后台是Spring项目,需要实现跨域访问,同时支持cookie。
Access-Control-Allow-Origin
我之前从网上找到实现跨域的方法,在后台添加了如下Filter:
public class CrossDomainFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
String referer = request.getHeader("referer");
if (StringUtils.isNotBlank(referer)) {
URL url = new URL(referer);
String origin = url.getProtocol() + "://" + url.getHost();
if(url.getPort()!=-1){
origin+=":"+url.getPort();
}
response.addHeader("Access-Control-Allow-Origin", origin);
} else {
response.addHeader("Access-Control-Allow-Origin", "*");
}
response.addHeader("Access-Control-Allow-Credentials", "true");
response.addHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE");
response.addHeader("Access-Control-Allow-Headers", "Content-Type");
filterChain.doFilter(request, response);
}
}
这样一般的跨域都没有问题了,但是当前端发起POST请求并向后台传Json格式数据时,浏览器控制台却报如下错误:
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.
而且我们注意到前端的这个请求如果不报错会发送两次,现在是第一次请求报了如上错误,第二次请求就没有发送了。
Preflight Request
经过查询,我们了解到浏览器发送的第一次请求是preflight request(预检验请求),用来获知服务器是否允许该跨域请求,如果允许,第二次才发送带数据的真实请求。
withCredentials
而通过查询上述的错误,我们也了解到前端需要和后台同步cookie,需要设置XMLHttpRequest
的withCredentials
属性为true
,同时要求后台设置响应头Access-Control-Allow-Credentials
为true
,并且响应头Access-Control-Allow-Origin
不能为*
,必须指定域名。
解决
显然,我的问题就在于响应头Access-Control-Allow-Origin
为*
。通过检查代码和前端请求,我发现preflight request
的请求头中没有Referer
,所以我代码没有走到设置域名的分支,于是我改用了从请求头的Origin
( 标识跨域资源请求)中获取请求源。修改后的代码如下:
public class CrossDomainFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
String referer = request.getHeader("origin");
if (StringUtils.isNotBlank(referer)) {
URL url = new URL(referer);
String origin = url.getProtocol() + "://" + url.getHost();
if(url.getPort()!=-1){
origin+=":"+url.getPort();
}
response.addHeader("Access-Control-Allow-Origin", origin);
response.addHeader("Access-Control-Allow-Credentials", "true");
} else {
response.addHeader("Access-Control-Allow-Origin", "*");
}
response.addHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE");
response.addHeader("Access-Control-Allow-Headers", "Content-Type");
filterChain.doFilter(request, response);
}
}
参考资料
[1] Access-Control-Allow-Origin https://blog.csdn.net/blogdevteam/article/details/84874036
[2] preflight request https://www.jianshu.com/p/b55086cbd9af
[3] withCredentials https://www.jianshu.com/p/af1fc0fab4c5
[4] 前端必备HTTP技能之HTTP请求头响应头中常用字段详解 https://www.jianshu.com/p/6e86903d74f7
[5] 彻底搞清referrer和origin 彻底搞清referrer和origin https://blog.csdn.net/zdavb/article/details/51161130