You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

授权令牌过期时如何刷新AVPlayer头部授权令牌?解决401问题

解决HLS流令牌过期后的401授权问题

你的代码存在两个核心问题导致401错误:一是请求头被覆盖丢失了令牌,二是没有处理令牌过期后的刷新重试逻辑。以下是修复后的实现方案:

修复后的ResourceLoaderDelegate代码

class ResourceLoaderDelegate: NSObject, AVAssetResourceLoaderDelegate {
    let originalURL: URL
    var token: String
    private let tokenQueue = DispatchQueue(label: "com.yourdomain.tokenQueue")
    private var retryCount = 0
    private let maxRetryCount = 1 // 限制重试次数,避免无限循环

    init(originalURL: URL, token: String) {
        self.originalURL = originalURL
        self.token = token
        super.init()
    }

    // 供外部调用的令牌刷新方法
    func updateToken(newToken: String) {
        tokenQueue.sync {
            self.token = newToken
        }
    }

    func resourceLoader(_ resourceLoader: AVAssetResourceLoader, shouldWaitForLoadingOfRequestedResource loadingRequest: AVAssetResourceLoadingRequest) -> Bool {
        guard let url = loadingRequest.request.url else {
            loadingRequest.finishLoading(with: NSError(domain: "Invalid URL", code: -1, userInfo: [NSLocalizedDescriptionKey: "请求URL无效"]))
            return false
        }

        tokenQueue.sync { [weak self] in
            guard let self = self else { return }
            // 先复制原有请求头,再添加Authorization令牌,避免覆盖
            var request = URLRequest(url: url)
            request.allHTTPHeaderFields = loadingRequest.request.allHTTPHeaderFields
            request.addValue("Bearer \(self.token)", forHTTPHeaderField: "Authorization")

            let task = URLSession.shared.dataTask(with: request) { [weak self] data, response, error in
                guard let self = self else { return }

                if let error = error {
                    loadingRequest.finishLoading(with: error)
                    return
                }

                guard let httpResponse = response as? HTTPURLResponse else {
                    let error = NSError(domain: "HTTP Error", code: -1, userInfo: [NSLocalizedDescriptionKey: "无效的HTTP响应"])
                    loadingRequest.finishLoading(with: error)
                    return
                }

                if httpResponse.statusCode == 401 {
                    // 令牌过期,尝试刷新并重试
                    if self.retryCount < self.maxRetryCount {
                        self.retryCount += 1
                        // 这里调用你的令牌刷新接口,示例中假设已经通过updateToken更新了新令牌
                        // 注意:实际刷新令牌需要异步处理,完成后重新发起请求
                        self.refreshToken { success in
                            if success {
                                // 刷新成功,重新处理当前请求
                                _ = self.resourceLoader(resourceLoader, shouldWaitForLoadingOfRequestedResource: loadingRequest)
                            } else {
                                let error = NSError(domain: "Auth Error", code: 401, userInfo: [NSLocalizedDescriptionKey: "令牌刷新失败"])
                                loadingRequest.finishLoading(with: error)
                            }
                        }
                    } else {
                        let error = NSError(domain: "Auth Error", code: 401, userInfo: [NSLocalizedDescriptionKey: "令牌过期且重试次数耗尽"])
                        loadingRequest.finishLoading(with: error)
                    }
                    return
                }

                guard httpResponse.statusCode == 200 else {
                    let error = NSError(domain: "HTTP Error", code: httpResponse.statusCode, userInfo: [NSLocalizedDescriptionKey: "HTTP状态码错误: \(httpResponse.statusCode)"])
                    loadingRequest.finishLoading(with: error)
                    return
                }

                if let data = data {
                    loadingRequest.dataRequest?.respond(with: data)
                }
                loadingRequest.finishLoading()
                self.retryCount = 0 // 重置重试计数
            }

            task.resume()
        }

        return true
    }

    // 示例:令牌刷新方法,替换为你的实际刷新逻辑
    private func refreshToken(completion: @escaping (Bool) -> Void) {
        // 调用后端刷新令牌接口,获取新令牌后调用updateToken
        // 示例模拟异步刷新
        DispatchQueue.global().asyncAfter(deadline: .now() + 0.5) { [weak self] in
            guard let self = self else {
                completion(false)
                return
            }
            // 假设获取到了新令牌
            let newToken = "your_new_refreshed_token"
            self.updateToken(newToken: newToken)
            completion(true)
        }
    }
}

关键修复点说明

  • 请求头顺序修正:先复制原有请求头,再添加Authorization字段,避免覆盖自定义令牌。
  • 令牌可变与线程安全:用tokenQueue保证令牌更新的线程安全,因为AVAssetResourceLoader的回调可能在后台线程执行。
  • 401重试逻辑:检测到401响应时触发令牌刷新,刷新成功后重试当前请求,并限制重试次数防止无限循环。
  • 外部令牌更新支持:提供updateToken方法,允许外部主动刷新令牌(比如提前在令牌过期前更新)。

使用注意事项

  1. 初始化AVAsset时,需要将资源加载器关联到delegate:
let delegate = ResourceLoaderDelegate(originalURL: hlsURL, token: initialToken)
let asset = AVURLAsset(url: hlsURL)
asset.resourceLoader.setDelegate(delegate, queue: DispatchQueue(label: "com.yourdomain.resourceLoaderQueue"))
  1. 实际项目中,refreshToken方法需要替换为你的后端令牌刷新接口调用,确保获取到有效的新令牌。
  2. 可以根据业务需求调整maxRetryCount,或者添加令牌过期时间预判,提前触发刷新避免401。

内容的提问来源于stack exchange,提问作者Narendra

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 13:35:00