Files
youle_app_ios_v2/ylgamehall/Source/Network/QiniuTokenSigner.swift
T
joywayer c3a0ddbf8b 3.B 修复:七牛 token 401 bad token(base64URL padding + scope 字段)
## 问题

录音 + 转码正常,但七牛上传报:
  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。
2026-06-24 02:09:28 +08:00

69 lines
2.9 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//
// QiniuTokenSigner.swift
// ylgamehall
//
// 七牛上传 token 客户端自签(HMAC-SHA1 + Base64URL)。
// 与 msext QiniuManager.m:200-230 generateUploadToken 等价算法。
//
// putPolicy 结构:
// { "scope": "iosaudio[:key]", "deadline": now + expiresIn }
// 签名流程:
// putPolicy JSON → Base64URL → HMAC-SHA1(secretKey) → Base64URL
// → `${AccessKey}:${sign}:${encodedPolicy}`
//
// ⚠️ 用 CryptoKitiOS 13+),不依赖 Qiniu SDK,纯 Swift 自签 token。
//
import Foundation
import CryptoKit
/// 七牛上传 token 自签。整体 nonisolatedactor 内可直接调(不需要 await MainActor.run)。
public enum QiniuTokenSigner {
/// 生成上传 token。
/// - Parameters:
/// - 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 {
_ = key // 参数保留以避免破坏调用方,但不再使用
// 1. scope 仅含 bucket(对齐 daoqi QiniuManager.m:207
let scope = QiniuConfig.bucketName
let deadline = Int(Date().timeIntervalSince1970 + expiresIn)
// 2. putPolicy JSONdaoqi 用默认 options:0,不强制排序)
let putPolicy: [String: Any] = [
"scope": scope,
"deadline": deadline
]
let policyData = (try? JSONSerialization.data(withJSONObject: putPolicy, options: [])) ?? Data()
// 3. Base64URL(putPolicy)
let encodedPolicy = base64URLEncode(policyData)
// 4. HMAC-SHA1(secretKey, encodedPolicy) → Base64URL
let secretKeyData = Data(QiniuConfig.secretKey.utf8)
let key = SymmetricKey(data: secretKeyData)
let signature = HMAC<Insecure.SHA1>.authenticationCode(
for: Data(encodedPolicy.utf8),
using: key
)
let encodedSign = base64URLEncode(Data(signature))
// 5. token = AccessKey:encodedSign:encodedPolicy
return "\(QiniuConfig.accessKey):\(encodedSign):\(encodedPolicy)"
}
/// 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: "_")
}
}