9

关于websocket跨域的一个奇怪问题

 3 years ago
source link: https://fredal.xin/websocket-cors-problem
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

关于websocket跨域的一个奇怪问题

发表于 2020-08-30   |   条评论   |   热度 °C

最近在建设websocket长连接网关,过程中遇到一件比较奇怪的事情,做下简单的记录。

需求十分的简单,websocket网关在做权限校验的时候期望复用现有登录逻辑的jwt-token。如下图所示,sso与websocket网关属于不同的二级域名,登录的jwt-token cookie的domain设置为*.xx.com。所以我们的期望是浏览器与websocket网关进行handshark请求时可以带上jwt-token cookie。

2020-08-30-120406.png

结果自然是不行的,服务端并没有收到来自*.xx.com的cookie。于是开始考虑可能和跨域行为有关系。

CORS 是一种用于解决跨域的w3c标准,全称为"跨域资源共享"(Cross-origin resource sharing)。它允许浏览器向跨源服务器,发出 XMLHttpRequest 请求,从而克服了 AJAX 只能同源使用的限制。CORS 基于 http 协议关于跨域方面的规定,使用时,客户端浏览器直接异步请求被调用端服务端,在响应头增加响应的字段,告诉浏览器后台允许跨域。

概括的说,CORS就是服务端对跨域权限的控制,由一组标准的header来控制客户端的跨域行为,不同浏览器对于CORS的实现均有不同。

常用的CORS header主要有:

  • Access-Control-Allow-Origin : 指示请求的资源能共享给哪些域,可以是具体的域名或者*表示所有域。

  • Access-Control-Allow-Credentials : 指示当请求的凭证标记为 true 时,是否响应该请求。

  • Access-Control-Allow-Headers : 用在对预请求的响应中,指示实际的请求中可以使用哪些 HTTP 头。

  • Access-Control-Allow-Methods: 指定对预请求的响应中,哪些 HTTP 方法允许访问请求的资源。

CORS处理请求的流程如下:

  1. 判断当前请求是否简单请求。

  2. 如果不是简单请求,则会使用OPTIONS方法先发起一个预检请求(PreFlight),预检请求通过返回的response里设置了对应的header并匹配上了才会进行下一步具体的请求。

  3. 预检请求后会发起实际请求,但会根据返回的response header来决定请求行为,例如根据服务端设置的Access-Control-Allow-Credentials值来决定请求是否携带当前域的cookie。

这里涉及到的简单请求和非简单请求的概念,那么简单请求和非简单请求有什么区别呢?若请求满足所有下述条件,则该请求可视为简单请求:

  1. 使用了下列 HTTP 方法:GET、HEAD、POST。

  2. 只用了以下header:Accept、Accept-Language、Content-Language、Content-Type(有额外限制)、DPR、Downlink、Save-Data、Viewport-Width、Width。

  3. 请求中的任意XMLHttpRequestUpload 对象均没有注册任何事件监听器;XMLHttpRequestUpload 对象可以使用 XMLHttpRequest.upload 属性访问。

  4. 请求中没有使用 ReadableStream 对象。

经过一番简单的科普,回到我们的问题上来。浏览器对websocket的handshark请求会不会应用同源策略呢。我们先不回答,先来看看如果CORS应用在websocket上会是什么样的。

首先一个websocket的握手连接报文大概如下:


GET / HTTP/1.1

Upgrade: websocket

Connection: Upgrade

Host: ws.xx.com

Origin: http://www.xx.com

Sec-WebSocket-Key: sB9cRrP/a9NdMgdcy2VJFX==

Sec-WebSocket-Version: 11

它和普通HTTP请求的区别是多了两行header


Upgrade: websocket

Connection: Upgrade

显然它们不属于CORS安全的header集合,自然浏览器会认为这不是一个"简单请求"。那么它会按照发起"预检请求",随后根据返回的response header来判断下一步行为。此处我们希望能带上当前域的cookie,那么按照CORS标准,我们需要在服务端做一些配置,让其支持CORS并带上Access-Control-Allow-Credentials为true的response header。

我们使用的是Netty来构建websocket网关,Netty支持CORS很简单:


CorsConfig corsConfig = CorsConfigBuilder.forAnyOrigin().allowNullOrigin().allowCredentials().build();

pipeline.addLast(new CorsHandler(corsConfig));

结果是什么呢?我们的websocket服务端正确拿到了*.xx.com的cookie,并完成了后续鉴权工作。

websocket需要CORS么?

所以真相是什么呢?websocket也需要CORS支持来避免跨域问题么?

google任何websocket与跨域相关的问题都会告诉你,websocket本身就是支持跨域的,websocket本身没有同源策略!也就是说,在第一幅图中,我们应该不作任何事就可以把xx.com的cookie带到ws.xx.com的websocket网关上去,这似乎和我们实际情况不符。

我们使用的是chrome,后来突发奇想试了下firefox与safari,结论是这两者不用配置任何CORS相关属性就可以把cookie带上。难道这是chrome的一个bug?翻了翻网络,找到了一个似乎可以应征的bug report: Cookies not sent in Websocket handshake with cookies blocked and domain whitelisted


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK