阅读 39

cookie跨域那些事儿

一个请求从发出到返回,需要浏览器和服务端的协调配合。浏览器要把自己的请求参数带给服务端,服务端校验参数之后,除了返回数据,也可能会顺便把请求是否缓存,cookie等信息告诉浏览器。当请求是跨域请求的时候,这个过程还要复杂一些。接下来咱们就看看跨域会有什么问题,又需要前后端进行怎样的配合。

普通跨域

我有一个朋友,叫小王。前端小王和后端同事小马准备联调一个登录的api。假设是/login;小王在把登录账号和密码都准备好之后,愉快的发起了post提交。结果很意外,请求的响应被浏览器拦截了,浏览器还贴心的在console上抛出了一个错误。
image
小王翻译了一下,原来是被CORS策略拦截掉了。这个策略大概意思是说,服务端如果允许不同origin的请求,那就需要在返回的response header里面带上Access-Control-Allow-Origin这个header。否则浏览器在拿到响应并发现响应头里没有这个header时,就会把响应给吞掉,而不会交给js进行下一步处理。

小王把这个事情告诉了小马,然后小马在返回的header中加上了

Access-Control-Allow-Origin: *
复制代码

现在小王终于可以拿到返回的结果了。

这里要注意,浏览器不是在请求阶段就对请求进行拦截,而是正常发出请求,拿到服务端的响应之后,开始查看响应header里面有没有Access-Control-Allow-Origin这个header,如果没有,响应的结果就不会到js那里去。

非简单请求的跨域

后来小王觉得在post中发送表单格式的body太麻烦,希望使用JSON格式的请求体提交。小马觉得就是几行代码的事,就同意了。但是小王改成JSON的消息体之后发现又被CORS拦截了,并抛出了下面的错误:
image

在上面的报错中,我们看到了 preflight 的单词。那这又是怎么回事呢?原来,修改请求体之后,这个跨域请求不再是简单请求了,需要在发起请求之前先进行 preflight 请求。那么什么是简单请求呢?

  • 请求方法包括GET, HEAD, POST
  • response header里面不能包含cors安全header以外的header。
  • Content-Type 只限于text/plain, multipart/form-data, application/x-www-form-urlencoded

由于json数据的content-type导致这个post请求不再是简单请求,而对于非简单请求,之前允许所有域名跨域访问是被禁止的。所以还是要修改Access-Control-Allow-Origin为特定的请求域名。在开发模式下,可能是http://localhost:3000之类的。

小马在重新修改Access-Control-Allow-Origin,小王又拿到了登录成功的结果。可以联调下一个api了。

带cookie的跨域

登录是基于session的,也就是说,登录成功后,server会通过set-cookie,将cookie设置到浏览器中,这样,下次访问同源下的api时,cookie就会被带上。

然而,奇怪的是,小王发现登录成功后,调用别的接口,cookie并没有被带上,导致server无法识别出用户信息,最终返回错误(状态码为401)。

withCredentials

原来,浏览器发起跨域请求的时候,是不会主动带上cookie的,如果一个请求需要cookie,需要开发者设置一个选项,以fetch api为例:

fetch('http://baidu.com:3000', {
    // ...
	credentials: true
})
复制代码

如果使用xhr api来请求,则需要这样写:

var invocation = new XMLHttpRequest();
var url = 'http://bar.other/resources/credentialed-content/';

function callOtherDomain(){
  if(invocation) {
    invocation.open('GET', url, true);
    invocation.withCredentials = true; // 带上cookie
    invocation.onreadystatechange = handler;
    invocation.send();
  }
}
复制代码

小王在设置请求之后又发起了一次请求。却发现cookie还是没有带上去。小王只好在MDN继续查看资料,发现在set-cookie时需要带一个sameSite的属性。

sameSite

sameSite是为了防止csrf攻击而产生的属性,如果不知道啥是CSRF攻击,可以看我这篇文章。由于我们需要在请求中带上cookie,所以需要在set-cookie时将cookie的sameSite设置为none;又由于将sameSite设置为none时,也需要将Secure设置上,所以请求需要基于https;

小王最后一次请求小马对api进行了上诉更改,服务器终于认出请求来自谁,并返回了正确的结果,跨域的踩坑之旅算是告一段落。

总结

很多时候,我们可能只会关注请求体是什么,响应有没有正确返回,而忽略了header部分。殊不知,header在缓存,web安全,浏览器正确解析结果中发挥了重要的作用,比如本文中的一系列Access-Control-Allow-*的header。

为了让web更安全,CORS还在不断地更新,比如这个提案,规定从公网到私网,或者从私网访问local network时,需要设置跨域头,Access-Control-Allow-Private-Network。希望本文能对您理解跨域有所帮助。(本文完)

参考资料

developer.mozilla.org/zh-CN/docs/…
www.ruanyifeng.com/blog/2019/0…
developer.mozilla.org/zh-CN/docs/…
developer.mozilla.org/en-US/docs/…
developer.mozilla.org/zh-CN/docs/…

文章分类
前端
文章标签