XMLHttpRequest CORS 考古

今天在用SockJS时,遇到浏览器对跨域请求报错:

Failed to load resource: Cannot use wildcard in Access-Control-Allow-Origin when credentials flag is true.

搜索发现这是一个浏览器阻止不安全的跨域访问的问题,我们来考一下古:

1993年,互联网正在高速发展,<img>这个标签被加入进来。

从那开始,网页变得漂亮多了。随着越来越多的标签也都加入进来,比如<script>

那时候,网站之间可以随便访问,很容易发生跨站攻击,于是就有了CORS。

考古完成,下面开始干活。我们看看今天的这个问题:

XMLHttpRequest.withCredentials

一番搜索发现,选项默认为false,浏览器跨域访问时,如果要携带Cookie等安全信息,就要设置这个选项为true

这是出于安全性的考虑,这样客户端可以避免Cookie等安全信息意外的泄漏给另一个域。

如果服务依赖Cookie等安全信息,就要满足:

  • XMLHttpRequest.withCredentialstrue
  • Access-Control-Allow-Origin精确匹配(这里的精确匹配是指协议、域名、端口全部都匹配)
  • Access-Control-Allow-Credentialstrue

Access-Control-Allow-Origin相信大家都很熟悉了,我们还是看看Access-Control-Allow-Credentials是干什么的:

Access-Control-Allow-Credentials

客户端可以控制安全信息的发送了,那么服务端是否想要呢?于是就有了Access-Control-Allow-Credentials头域。

当客户端设置withCredentials等于true时,如果服务器不返回Access-Control-Allow-Credentials: true,请求就失败了。

为了预判服务器是否支持CORS,浏览器会进行preflight:

那么,今天的问题怎么处理呢?

Case by case

由于我是使用SockJS作为传输层,安全认证所需的信息协议层,所以我不需要客户端发送Cookie等安全信息。

但是SockJS在跨域时,只要发现浏览器支持withCredentials就会设置为true

1
2
3
4
5
6
var cors = false;
try {
  cors = 'withCredentials' in new XHR();
} catch (ignored) {
}
AbstractXHRObject.supportsCORS = cors;
1
2
3
if ((!opts || !opts.noCredentials) && AbstractXHRObject.supportsCORS) {
    this.xhr.withCredentials = true;
}

所以,我试着workaround了一下:

Workaround

加载SockJS前,shim XMLHttpRequest.send(),把withCredentials改为false:

1
2
3
4
5
6
7
8
(function(xhr) {
    window.shimmedXHR = XMLHttpRequest.prototype;
    send = xhr.send;
    xhr.send = function(data) {
        this.withCredentials = false;
        return send.apply(this, arguments);
    };
})(XMLHttpRequest.prototype);

加载SockJS后,再改回来:

1
2
3
(function(xhr) {
    xhr = window.shimmedXHR || xhr;
})(XMLHttpRequest.prototype);

测试一下,可以跨域进行SockJS连接了。

updatedupdated2021-05-272021-05-27