如何用RxSwift处理凭证刷新?解决多请求重复授权问题
解决方案:用共享Token流控制并发授权请求
核心思路是维护一个全局共享的Token Observable,确保同一时间只有一个授权请求在执行,所有需要Token的API请求都等待这个流输出有效Token,同时支持Token过期后的二次刷新。
具体实现步骤:
- 创建共享的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 } }
- 实现原子化的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) // 标记授权失败 }) } }
- 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
相关产品推荐
相关产品推荐

