搞了一下午,终于把这个坑填上了。记录一下,免得下次又踩。

事情是这样的:在 HarmonyOS NEXT API15 上做 WebSocket 连接,按照官方文档用 webSocket.createWebSocket() 创建实例,再调 ws.connect(url, options) 建立连接。options.header 里传了 Authorization: 'Bearer xxx',编译一切正常,但服务端死活收不到这个 header,直接返回 401。

诡异的是,换成 X-Custom-Header 服务端就能正常收到。

这第一反应肯定是:系统在偷偷过滤 Authorization 头?还是说遇到了什么玄学 bug?

排查过程:从怀疑人生到定位问题

先确认不是自己的锅。代码大概长这样:

import { webSocket } from '@kit.NetworkKit';

let ws = webSocket.createWebSocket();
ws.connect('wss://api.example.com/ws', {
  header: {
    'Authorization': 'Bearer eyJhbGciOiJIUzI1...',
    'X-Custom-Header': 'hello'
  }
}, (err, value) => {
  // err 没报错,连接看起来建立成功了
  // 但服务端那边日志显示:没收到 Authorization
});

编译通过,没报错,没有警告。抓包看握手请求,X-Custom-Header 老老实实躺在那里,而 Authorization 却凭空消失了。

我一度以为是 key 拼错了、大小写问题、甚至怀疑是 token 里的特殊字符被吞了。全试了一遍,都不是。

真相:系统确实过滤了 Authorization

话说回来,这其实不是 HarmonyOS 独有的"骚操作"。

浏览器原生的 WebSocket API 根本就不让你设置任何自定义 header——new WebSocket(url) 连传 header 的口子都没有。原因很简单:WebSocket 握手本质上是一次 HTTP Upgrade 请求,而某些 header(比如 AuthorizationCookieOriginHost)属于"受保护的请求头",平台出于安全考虑会限制客户端随意篡改。

HarmonyOS 的 @kit.NetworkKit 比浏览器大方一些——它开放了 options.header 让你传自定义头。但即便如此,系统底层仍然会过滤掉一部分敏感 header,Authorization 就是其中之一。

这不是 bug,是平台的安全策略。

坦白讲,这个设计思路能理解:如果任意 App 都能在 WebSocket 握手里伪造 Authorization 头,配合中间人攻击或者恶意代理,安全风险不小。但对于开发者来说,文档里没写这一条,真的会坑死人的。

怎么解决?三种方案

既然 Authorization 头被系统拦了,那就换个通道把 token 送过去。

方案一:URL Query 参数(最简单)

把 token 拼到 URL 里:

ws.connect(`wss://api.example.com/ws?token=eyJhbGciOiJIUzI1...`, {
  header: {}
}, callback);

服务端从 query string 里取 token 做校验。简单粗暴,但 token 会出现在 URL 里,日志、代理、CDN 都可能记录,安全性要打折扣。适合内部服务或者对安全要求没那么高的场景。

Three-panel ink-note comparison of WebSocket authe

方案二:Sec-WebSocket-Protocol 子协议(推荐)

这个方案比较骚,但是业界公认的"正规旁门左道":

let ws = webSocket.createWebSocket();
// 把 token 塞进子协议字段
ws.connect('wss://api.example.com/ws', {
  header: {
    'Sec-WebSocket-Protocol': 'Bearer, eyJhbGciOiJIUzI1...'
  }
}, callback);

Sec-WebSocket-Protocol 是 WebSocket 协议标准里的一部分,系统一般不会过滤。服务端在握手时从这个 header 里解析出 token,校验通过后在响应里回传一个选定的子协议。Spring、Node.js、Go 等主流框架都支持这种玩法。

不过要注意:这个字段的设计初衷毕竟是"协商子协议",拿来传 token 多少有点"借壳上市"的味道,服务端需要配合改造。

方案三:连接建立后发消息认证(最稳)

先不管认证,连上再说。连接成功后立刻发一条认证消息:

ws.connect('wss://api.xxxxx.com/ws', {}, (err, value) => {
  if (!err) {
    // 连接成功,立刻发认证消息
    ws.send(JSON.stringify({
      type: 'auth',
      token: 'eyJhbGciOiJIUzI1...'
    }));
  }
});

服务端收到 auth 类型的消息后做 token 校验,校验失败就主动断开连接。这是最灵活也最安全的方案,不依赖任何 HTTP header,但需要服务端和客户端约定好认证协议。

三种方案怎么选?

方案 难度 安全性 服务端改动 推荐场景
URL Query 一般 快速验证、内部服务
Sec-WebSocket-Protocol 较好 生产环境、token 较长
连接后发消息 最好 对安全要求高的场景

我的建议是:如果只是快速跑通验证,URL Query 最快;如果要上生产,优先考虑"连接后发消息"的方案,干净利落。

写在最后

说实话,HarmonyOS 的 WebSocket API 文档在 header 过滤这块确实写得不够细。options.header 既然开放了自定义能力,就应该明确标注哪些 header 会被系统拦截,免得开发者自己一个个试错。

如果你也遇到了同样的问题,不用怀疑自己,也不用怀疑是不是 bug——这就是平台的"安全特性"。选个合适的替代方案,继续干活就好。

有问题欢迎评论区交流,或者你有更好的方案也可以分享一下。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐