From c3a0ddbf8bf67296311a9db3789126c99def9a31 Mon Sep 17 00:00:00 2001 From: joywayer Date: Wed, 24 Jun 2026 02:09:28 +0800 Subject: [PATCH] =?UTF-8?q?3.B=20=E4=BF=AE=E5=A4=8D=EF=BC=9A=E4=B8=83?= =?UTF-8?q?=E7=89=9B=20token=20401=20bad=20token=EF=BC=88base64URL=20paddi?= =?UTF-8?q?ng=20+=20scope=20=E5=AD=97=E6=AE=B5=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 问题 录音 + 转码正常,但七牛上传报: status: 401 / error: bad token / QiniuUploadError error 1 ## 根因 逐字段对照 daoqi QiniuManager.m:202-236 generateUploadToken,发现两处 关键不一致: ### 1. base64URL padding:新外壳删了 `=`,daoqi 保留 daoqi urlsafeBase64EncodeData:240-245 只替换 `+→-` 和 `/→_`,**保留 末尾 `=` padding**。 新外壳额外做了 `=→空` 替换,本意符合 RFC 4648 §5 base64URL 的可选 padding 删除,但七牛后端 decode token 时**依赖 padding 还原 base64**, 删 `=` 会让 HMAC 验签数据流不一致 → 401 bad token。 ### 2. scope 字段:新外壳带了 `:key` 后缀,daoqi 只用 bucket daoqi QiniuManager.m:207 putPolicy.scope = bucketName(限定 bucket, 允许任意 key)。 新外壳之前传 key 参数后构造 `bucket:key`(限定 key 精确匹配),与 daoqi 行为不一致。带 `:key` 后缀也是七牛合法 scope,但七牛后端 校验更严,且无必要——key 已经在 putFile API 单独传递,token 内不必 重复声明。 ## 修复 - base64URLEncode:移除 `=→空` 替换 - uploadToken(key:):key 参数标记为废弃但保留(避免破坏调用方), putPolicy.scope 改为只用 bucket - 顺带:JSONSerialization 不再强制 sortedKeys(daoqi 用 options:0) ## 验证 BuildProject 通过 0 错误。真机重试录音上传应不再 401。 --- .../Source/Network/QiniuTokenSigner.swift | 27 +++++++++---------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/ylgamehall/Source/Network/QiniuTokenSigner.swift b/ylgamehall/Source/Network/QiniuTokenSigner.swift index d88726a..cc373e4 100644 --- a/ylgamehall/Source/Network/QiniuTokenSigner.swift +++ b/ylgamehall/Source/Network/QiniuTokenSigner.swift @@ -22,25 +22,24 @@ public enum QiniuTokenSigner { /// 生成上传 token。 /// - Parameters: - /// - key: 上传文件 key(可空:scope = bucket,allow override;非空:scope = bucket:key) + /// - key: 已废弃参数;保留签名兼容性,但不再写入 putPolicy。daoqi + /// QiniuManager.m:207 的 putPolicy.scope 仅含 bucket(限定 bucket、 + /// 允许任意 key),与新外壳上传 key 是分离的两个维度。带 `:key` 后 + /// 缀(限定 key 精确匹配)会让七牛后端校验更严,且与 daoqi 行为不一致 /// - expiresIn: token 有效时长(默认 3600 秒) public nonisolated static func uploadToken(key: String? = nil, expiresIn: TimeInterval = 3600) -> String { - // 1. 拼 scope - let scope: String - if let key, !key.isEmpty { - scope = "\(QiniuConfig.bucketName):\(key)" - } else { - scope = QiniuConfig.bucketName - } + _ = key // 参数保留以避免破坏调用方,但不再使用 + + // 1. scope 仅含 bucket(对齐 daoqi QiniuManager.m:207) + let scope = QiniuConfig.bucketName let deadline = Int(Date().timeIntervalSince1970 + expiresIn) - // 2. putPolicy JSON(注意 key 顺序与 msext 等价:scope / deadline, - // 实际七牛后端不要求顺序,但保持与 msext 一致便于排错) + // 2. putPolicy JSON(daoqi 用默认 options:0,不强制排序) let putPolicy: [String: Any] = [ "scope": scope, "deadline": deadline ] - let policyData = (try? JSONSerialization.data(withJSONObject: putPolicy, options: [.sortedKeys])) ?? Data() + let policyData = (try? JSONSerialization.data(withJSONObject: putPolicy, options: [])) ?? Data() // 3. Base64URL(putPolicy) let encodedPolicy = base64URLEncode(policyData) @@ -58,12 +57,12 @@ public enum QiniuTokenSigner { return "\(QiniuConfig.accessKey):\(encodedSign):\(encodedPolicy)" } - /// Base64URL: 标准 Base64 把 + → -、/ → _、去掉末尾 = - /// 与七牛 SDK `QNUrlSafeBase64.encodeData:` 等价 + /// Base64URL: 标准 Base64 把 + → -、/ → _,**保留末尾 `=` padding**。 + /// 对齐 daoqi QiniuManager.m:240-245 `urlsafeBase64EncodeData:`,七牛后端 + /// decode token 时依赖 padding 还原 base64;删 `=` 会触发 401 bad token。 private nonisolated static func base64URLEncode(_ data: Data) -> String { data.base64EncodedString() .replacingOccurrences(of: "+", with: "-") .replacingOccurrences(of: "/", with: "_") - .replacingOccurrences(of: "=", with: "") } }