跳到主要内容

第三方客户端接入:网易云登录态同步

面向 CurrentMusicX 等第三方客户端。目标:用户在本站登录 CurrentMusic 账号后, 客户端自动沿用该账号已绑定的网易云登录态,不需要用户再登录一次网易云。

核心原则​

服务端不向第三方客户端下发网易云 cookie(MUSIC_U 等)。

网易云登录态保存在服务端(按 CurrentMusic 用户维度)。客户端拿 CurrentMusic 的 Bearer token 调用 /cm/ncm/* 的 T1/T2 接口,服务端会自动注入该用户的绑定 cookie。 已有服务端绑定可直接沿用,不需要下载 Cookie。对于在 X 本机直接登录产生的新凭据,使用下文专用导入接口写入服务端;服务端仍不会将 Cookie 下发给其他客户端。

1. 登录 CurrentMusic 账号​

用与网页端相同的账号接口(POST /cm/auth/login)或邮箱验证码登录 (POST /cm/auth/login/email),拿到:

{ "token": "…", "user": { "id": 150, "username": "…", "nickname": "…" } }

之后所有请求带:

Authorization: Bearer <token>

2. 一次调用判断网易云登录态​

GET /cm/ncmbind/session

{
"bound": true,
"valid": true,
"stale": false,
"profile": { "uid": 4994500926, "nickname": "Faded-Flower", "avatar": "https://…" },
"lastSync": 1792220000,
"lastSyncCount": 36,
"note": "登录态有效"
}
字段含义
bound该 CurrentMusic 账号是否已绑定网易云
valid绑定 cookie 当前是否仍然有效(true 有效 / false 已失效 / null 上游查询失败,稍后再试)
stale服务端标记的失效状态(与 valid 同步更新)
profile网易云昵称 / 头像 / uid(bound=false 时为 null)
note可直接展示给用户的说明

客户端行为建议:

  • bound && valid → 直接进入音乐功能,不要再弹网易云登录;
  • bound && !valid → 提示「网易云登录已失效」,给「重新绑定」按钮,或先尝试 POST /cm/ncmbind/refresh;
  • !bound → 引导绑定(见下节)。

只要用户曾在网页端 / App 绑定过网易云,这里就会返回 bound=true —— 这就是 「自动同步登录态」的全部实现方式,不需要额外传 cookie。

3. 未绑定时:二维码绑定(推荐)​

步骤请求返回
1POST /cm/ncmbind/qr/key{ "key": "…" }
2GET /cm/ncmbind/qr/img?key=<key>{ "qrimg": "data:image/png;base64,…" }
3每 2~3 秒轮询 GET /cm/ncmbind/qr/check?key=<key>{ "code": 801/802/803/800 }

状态码(网易云约定):

code含义客户端动作
801等待扫码继续轮询
802已扫码,等待确认提示「请在手机上确认」
803登录成功并已绑定停止轮询,读取返回的 profile,重新调 /ncmbind/session
800二维码过期停止轮询,重新取 key

二维码 key 与服务端会话绑定:生成后只能由同一用户轮询。

4. 未绑定时:手机验证码绑定​

步骤请求说明
1POST /cm/ncmbind/phone/code { "phone": "138…", "ctcode": "86" }60 秒冷却
2POST /cm/ncmbind/phone/login { "phone": "138…", "captcha": "1234", "ctcode": "86" }成功即绑定

本机登录同步至 CurrentMusic(X 侧)​

X 扫码/手机登录可能直接连接网易云并将凭据保存在设备本地。这与服务端绑定是两份状态。 新版接入流程在本机登录成功后自动调用以下接口;旧版本 X 不会因后端上线而自动具备此能力。 已有本机有效登录在升级、启动或登录 CurrentMusic 后会静默同步,无需再次扫码或手动确认。切换账号或服务器后同步至当前账号与当前所选 HTTPS 服务器;绑定页保留「同步绑定」入口。

POST /cm/ncmbind/import​

使用 HTTPS,并携带发起绑定时同一 CurrentMusic 账号的 Authorization: Bearer <token>。 只把凭据发往该账号当时选定的服务器;禁止传入 URL 查询参数、输出到日志或发送给无关服务。

{
"cookie": "MUSIC_U=<device-session>; __csrf=<csrf>",
"expectedNcmUid": 12345
}
  • cookie:必填字符串,最多 16384 个 ASCII 字符,只接受 MUSIC_U、__csrf、NMTID、MUSIC_A,其中 MUSIC_U 必填;不接受重复键、控制字符或其他 Cookie。
  • expectedNcmUid:必填正整数,为本机刚登录的网易云 UID,用于与服务端验证结果比对。
  • 服务端向网易云验证 Cookie,昵称、头像和 UID 均使用验证结果,不信任客户端伪造资料。
  • 只更新 Bearer 对应的 CurrentMusic 用户;成功响应不含 Cookie,使用 Cache-Control: no-store。
  • 同一网易云账号重复同步保留歌单导入进度;换绑其他网易云账号时清除旧绑定的红心歌单定位和同步统计,并使相关缓存失效。已导入歌单快照保留。

成功响应结构:

{"ok":true,"bound":true,"profile":{"uid":12345,"nickname":"示例昵称","avatar":"https://example.com/avatar.jpg"}}
HTTP 状态含义
400参数、Cookie 格式或期望 UID 无效
401CurrentMusic 未登录,或网易云登录凭据失效;应区分错误文案,网易云失效不表示本站会话失效
409Cookie 对应的网易云账号与 expectedNcmUid 不一致;保留原服务端绑定
429请求限流
502网易云验证失败或超时;保留原服务端绑定

客户端在账号、服务器或本机网易云登录态变化时停止旧同步,不重试旧账号的凭据。 同步失败保留本机登录,后台在 5 秒、30 秒、120 秒间隔后有限重试,切换账号/服务器/本机登录态会取消旧任务。一次运行中,同一会话与网易云登录态同步成功后不重复上传。绑定页也提供重试入口;成功后同一账号的 Web/CurrentMusic App 通过 GET /cm/ncmbind 或 /cm/ncmbind/session 即可读到绑定。 这里同步的是绑定登录态,歌单快照仍通过 POST /cm/ncmbind/sync 单独导入。 X 的「退出本机」仍只清理本机凭据,服务端绑定保留;需要全端解绑时使用本站的解绑入口。

5. 绑定后:正常调用音乐接口​

同一条 Bearer token,调用 T1/T2 接口即可,服务端自动带上网易云 cookie:

GET /cm/ncm/user/playlist?uid=<ncmUid>&limit=200
GET /cm/ncm/recommend/resource
GET /cm/ncm/like?ids=…
POST /cm/ncm/playmode/intelligence/list

未绑定 / 登录态失效时,这些接口会返回 401 与明确文案 (请先绑定网易云账号后再使用该功能),据此引导用户去绑定或刷新。

6. 维护接口​

接口用途
POST /cm/ncmbind/refreshcookie 续期,成功后可继续使用,无需重新扫码
GET /cm/ncmbind轻量绑定信息(profile / stale / 上次同步),不查上游
POST /cm/ncmbind/sync把网易云歌单同步为本站歌单快照
DELETE /cm/ncmbind解绑(已导入歌单保留为本地快照)

7. 常见问题​

Q:能不能给一个直接返回 cookie 的接口? 不能。cookie 等于网易云账号的完整登录凭据;下发到客户端后无法审计、无法限权、 泄露即失控。服务端代理 T1/T2 已经能覆盖播放、歌单、红心等全部账号级功能。

Q:X 登录了 CurrentMusic 账号,为什么 bound=false? 说明该 CurrentMusic 账号本身没有绑定过网易云(或曾在别处解绑)。让用户走一次第 3/4 节的绑定流程即可,绑定是账号级的,之后网页端、App、X 都会共享同一份登录态。

Q:valid=null 怎么办? 上游暂时查询失败(超时/限流),不代表登录态失效。稍后重试,或直接调用音乐接口验证。

8. 参考实现(伪代码)​

suspend fun ensureNcmSession(token: String): NcmSession {
val s = api.get("/cm/ncmbind/session", bearer = token)
return when {
s.bound && s.valid == true -> s // 直接可用
s.bound && s.valid == false -> { // 已绑定但失效
runCatching { api.post("/cm/ncmbind/refresh", bearer = token) }
api.get("/cm/ncmbind/session", bearer = token)
}
else -> showBindSheetAndWait() // 未绑定 → 二维码/手机绑定
}
}