带cookie的CORS

Posted on Sun 12 March 2017 in Frontend

关于

不得不说,这其实是一个非常悲惨的故事,作为自己25岁生日的礼物,稍微惨了一点。

最近一时兴起,用Vue.js写了一个很简单的前端项目,也算是希望能在离校之前给自己的学校留下一点东西。

起因

之前一切都很顺利,加feature,修bug,都没什么太大的困难,直到开始着手解决认证问题。

受限于各方面的限制,我写的前端所服务的后台服务是不能直接在校外访问的,然而不管是在校生还是已毕业校友都有在校外访问的需要,因此折中一下,这个服务需要认证才能访问。 认证是怎么做的呢?是一个比较传统的方案:先重定向到一个集中认证服务,登录完之后返回后台服务所在的域名,通过后台服务与认证服务交互,确认用户的登录情况,记录用户的身份,设置session,之后用cookie来识别用户的登录情况。

这部分其实与我的前端App没什么关系,因为不管是认证服务还是后台服务都是已经写好在运行的,我只要在访问后台服务的时候捕捉错误信息,正常跳转到认证服务,等待后台服务把用户重定向回来就行,因此与我来说看上去并没有什么关系。

然而问题在于,运行环境的服务器不由我管理,我也不愿意在运行环境下调试,因此我在自己的VPS和域名下部署了一套staging环境用于线上测试,自己开发则在本机用localhost开发,拜托运行的老师在nginx里加了CORS的HTTP headers,一切挺顺利,看上去也很合理,然而在折腾cookie的时候却出了问题。

兴起

虽然没有什么必须要用Fetch API的理由,但是本着新东西要体验一下的原则,这个项目里还是用了fetch来替代xhr。 上面说了,认证系统是靠cookie来识别用户身份的,那么我必须保证发给后端的请求里要带上cookie,然而跨域请求显然是不会带上cookie的。 简单搜索了一下,找到了这个,简单明了。 于是加上credentials这个参数,结果chrome上app跑起来,用户请求被重定向到了认证服务,什么鬼。翻了一下调试工具,发现访问后台的时候没有带cookie,直接用浏览器打开后台url,却能正常访问,cookie什么都不少。

这时候我就开始郁闷了:咦,spec理解错了?

于是乎,我开始认真地看了github的fetch polyfill文档,好像没差什么呀。 接着是MDN以及Google Developers,依然看不出来自己的调用方式有什么问题。

于是我开始脑洞大开,难道

To cause browsers to send a request with credentials included.

是指把源domain的credentials带上?那岂不是密码满天飞?

这时我依稀记起了polyfill的文档里提到了credentialssame-origin时行为和xhr是一致的,于是我觉得有必要复习一下xhr的行为。

于是重新看了MDN,以及这篇博客。 结论是自己的理解没有问题。

发展

这个时候我已经一头雾水了,出错其实不可怕,可怕的是出错了居然没有错误信息。 这就像你写了个int x = 1,编译运行发现x里存了个0,开了所有warning还是什么报警都没有。

于是我觉得编译器也不能相信了:

难道是chrome的实现有问题?由于特性新,我一直非常喜欢在chrome上写前端,要是特性实现有问题,那实在太可怕了,而且搜了半天都没见着issue,实在不可理解。

难道是服务器端给的headers有问题,导致CORS实际上并没有生效?或者只是部分生效?然而没看出什么问题。

难道是我又手贱把单词敲错了?之前因为手贱坑自己的事情并不少见。

于是我打开httpbin,开始各种尝试(不得不说,httpbin确实挺好用的,想要的东西基本都有,而且API简单,返回的结果里还带了很标准的CORS headers,很适合用来尝试一些东西)。

fetch换成xhr,也没带cookie;反复对比和增删HTTP headers,始终没效果;把标准里的样例直接copy进来,以防自己敲错,依然未果。就这样,我大概从下午两三点一直折腾到了晚上十二点,精疲力尽。

高潮

当我都快睡着的时候,一时兴起,准备装个chrome-dev试试,安装的过程中无事,打开了尘封多年的firefox,在console里敲了几段快背下来的测试代码,结果。cookie带上了。

啥?chrome实现有bug?正好chrome-dev装好了,赶紧在chrome-dev里试了一下,依然没有带上cookie。这时候思路开始清晰了,在chrome里开了guest用户,把自己的所有配置都禁掉,一测试,cookie带上了。

我此时头都炸了,哪个傻逼插件坑了我半天!打开扩展列表,把可疑的扩展都禁掉,再试了一次,发现还是不行。打开设置,搜了一下cookie,在content settings里找到了一个打着勾的Block third-party cookies and site data,发现自己完全看不懂是什么意思。尝试禁用,这次终于好了。果然最后坑自己的傻逼通常都是自己。

这时才依稀想起,几年前Google似乎是问了我一下,要不要禁用什么什么cookie,避免被追踪,当时糊涂地点了是。

总结

  1. 自己还是太年轻,换浏览器测试这样的事情都没想到;
  2. 测试的时候最好还是别用自己平常用的浏览器,有些上古时期加的玄学配置容易忘记,用干净的浏览器比较好;
  3. 遇到事情还是不能急,有些问题只要仔细分析一下,是可以不动手就找到一些蛛丝马迹的;
  4. 解决问题要讲究效率。

正文

好,上面都是扯淡,看黑板。

如果你要让自己的CORS里带上cookie,完整的步骤是这样的:

  • 服务器端需要加一些headers,以nginx为例,建议用如下配置:
add_header Access-Control-Allow-Origin $http_origin always;
add_header Access-Control-Allow-Credentials true always;

如果想看实例,建议看httpbin的。

  • 客户端需要加一些参数,比如如果是xhr需要设xhr.withCredentials为true,如果是fetch需要设credentialsinclude
  • 确保用户的浏览器没有开Block third-party cookies这个选项,或者已经把你的站点加入白名单,这个可以提示用户自己去改。

这篇博客就这么点,晚安。