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

如何用RxSwift处理凭证刷新?解决多请求重复授权问题

解决方案:用共享Token流控制并发授权请求

核心思路是维护一个全局共享的Token Observable,确保同一时间只有一个授权请求在执行,所有需要Token的API请求都等待这个流输出有效Token,同时支持Token过期后的二次刷新。

具体实现步骤:

  1. 创建共享的Token存储流
    用BehaviorSubject缓存当前Token状态,初始值从HTTPCookieStorage.shared读取已有Token:
import RxSwift
import RxCocoa

class AuthManager {
    static let shared = AuthManager()
    private let disposeBag = DisposeBag()
    // 用BehaviorSubject缓存Token,nil表示未授权/Token过期
    private let tokenSubject = BehaviorSubject<String?>(value: currentValidToken())
    // 对外暴露不可变的有效Token流
    var validToken: Observable<String> {
        tokenSubject
            .filter { $0 != nil }
            .map { $0! }
            .flatMapLatest { [weak self] token in
                // 检查Token是否过期,过期则触发刷新
                if self?.isTokenExpired(token) ?? true {
                    return self?.refreshToken() ?? .empty()
                } else {
                    return .just(token)
                }
            }
            .share(replay: 1, scope: .whileConnected)
    }
    private var isRefreshing = false
    private let refreshLock = NSRecursiveLock()

    // 从Cookie读取当前Token
    private func currentValidToken() -> String? {
        guard let cookies = HTTPCookieStorage.shared.cookies else { return nil }
        return cookies.first { $0.name == "auth_token" }?.value
    }

    // 根据Cookie过期时间判断Token是否失效
    private func isTokenExpired(_ token: String) -> Bool {
        guard let cookies = HTTPCookieStorage.shared.cookies else { return true }
        guard let cookie = cookies.first { $0.value == token } else { return true }
        return cookie.expiresDate?.compare(Date()) == .orderedAscending
    }
}
  1. 实现原子化的Token刷新逻辑
    给AuthManager添加刷新方法,用锁确保同一时间只有一个刷新请求:
extension AuthManager {
    func refreshToken() -> Observable<String> {
        refreshLock.lock()
        defer { refreshLock.unlock() }

        // 若正在刷新,直接等待当前刷新结果
        if isRefreshing {
            return tokenSubject.filter { $0 != nil }.map { $0! }
        }

        isRefreshing = true

        // 发起授权API请求
        return AuthAPI.authorize()
            .do(onNext: { [weak self] newToken in
                // 更新Cookie和Token流
                self?.tokenSubject.onNext(newToken)
                self?.isRefreshing = false
            }, onError: { [weak self] error in
                self?.isRefreshing = false
                self?.tokenSubject.onNext(nil) // 标记授权失败
            })
    }
}
  1. API请求统一依赖Token流
    所有需要Token的API请求,先订阅validToken拿到有效Token再发起请求,同时处理401过期重试:
class APIManager {
    static let shared = APIManager()
    private let disposeBag = DisposeBag()

    func fetchUserData() -> Observable<UserData> {
        AuthManager.shared.validToken
            .flatMapLatest { token in
                UserAPI.fetchData(withToken: token)
                    .catch { [weak self] error -> Observable<UserData> in
                        // 遇到401则刷新Token后重试原请求
                        if (error as? APIError) == .unauthorized {
                            return AuthManager.shared.refreshToken()
                                .flatMap { newToken in
                                    UserAPI.fetchData(withToken: newToken)
                                }
                        } else {
                            return .error(error)
                        }
                    }
            }
    }
}

方案优势:

  • 杜绝并发授权请求:通过锁和状态标记,确保同一时间只有一个刷新请求执行,多并发API请求会等待同一个刷新结果。
  • 支持二次刷新:无论是初始检查到Token过期,还是API返回401,都会触发刷新,所有等待的请求都会拿到新Token。
  • 自动复用有效Token:BehaviorSubject缓存当前Token,无需每次请求都检查Cookie,除非Token确实过期。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.20 11:57:26