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

如何在Moya中更换Bearer Token及实现自动刷新?

Handling Automatic Token Refresh in Moya for 403/401 Responses

Great question—yes, Moya has built-in tools to handle automatic token refresh when you hit an unauthorized (403/401) response. Here's a practical, production-ready implementation:

1. Create a Centralized Token Manager

First, make a singleton to handle storing, updating, and refreshing your tokens. This keeps all token-related logic in one place:

class TokenManager {
    static let shared = TokenManager()
    private(set) var accessToken: String?
    private(set) var refreshToken: String?
    
    // Update and store tokens (use Keychain instead of UserDefaults for security!)
    func updateTokens(accessToken: String, refreshToken: String) {
        self.accessToken = accessToken
        self.refreshToken = refreshToken
        // Add Keychain persistence logic here
    }
    
    // Refresh token via your auth API
    func refreshAccessToken(completion: @escaping (Result<String, Error>) -> Void) {
        guard let refreshToken = refreshToken else {
            completion(.failure(NSError(domain: "TokenError", code: -1, userInfo: [NSLocalizedDescriptionKey: "No refresh token available"])))
            return
        }
        
        // Replace AuthAPI with your actual auth endpoint enum
        let authProvider = MoyaProvider<AuthAPI>()
        authProvider.request(.refreshToken(refreshToken: refreshToken)) { result in
            switch result {
            case .success(let response):
                do {
                    let tokenResponse = try response.map(TokenResponse.self) // Your custom token model
                    self.updateTokens(accessToken: tokenResponse.accessToken, refreshToken: tokenResponse.refreshToken)
                    completion(.success(tokenResponse.accessToken))
                } catch {
                    completion(.failure(error))
                }
            case .failure(let error):
                completion(.failure(error))
            }
        }
    }
}

2. Use RequestRetrier to Handle Retries

This protocol lets you intercept failed requests, check for unauthorized status codes, and trigger a token refresh before retrying:

class AuthRetrier: RequestRetrier {
    private let tokenManager = TokenManager.shared
    private var isRefreshing = false
    private var pendingRequests: [RetryCompletion] = []
    
    func shouldRetry(_ request: RequestType, dueTo error: Error, completion: @escaping RetryCompletion) {
        // Check if the error is a status code issue
        guard let moyaError = error as? MoyaError,
              case .statusCode(let response) = moyaError else {
            completion(false, 0.0) // Don't retry non-status-code errors
            return
        }
        
        // Adjust status codes to match your backend (401 is also common for unauthorized)
        if response.statusCode == 403 || response.statusCode == 401 {
            if isRefreshing {
                // Queue the request to retry once refresh is done
                pendingRequests.append(completion)
                return
            }
            
            isRefreshing = true
            tokenManager.refreshAccessToken { [weak self] result in
                guard let self = self else { return }
                self.isRefreshing = false
                
                switch result {
                case .success:
                    // Retry all pending requests + the original one
                    self.pendingRequests.forEach { $0(true, 0.0) }
                    self.pendingRequests.removeAll()
                    completion(true, 0.0)
                case .failure:
                    // Fail all pending requests + the original one
                    self.pendingRequests.forEach { $0(false, 0.0) }
                    self.pendingRequests.removeAll()
                    completion(false, 0.0)
                }
            }
        } else {
            completion(false, 0.0) // Don't retry other status codes
        }
    }
}

3. Use RequestAdapter to Inject Tokens Automatically

This protocol adds the Bearer token to every outgoing request without manual setup:

class AuthAdapter: RequestAdapter {
    private let tokenManager = TokenManager.shared
    
    func adapt(_ urlRequest: URLRequest, for session: Session, completion: @escaping (Result<URLRequest, Error>) -> Void) {
        var adaptedRequest = urlRequest
        if let accessToken = tokenManager.accessToken {
            adaptedRequest.addValue("Bearer \(accessToken)", forHTTPHeaderField: "Authorization")
        }
        completion(.success(adaptedRequest))
    }
}

4. Wire Up Your Moya Provider

Initialize your main provider with the adapter and retrier to activate the workflow:

let authAdapter = AuthAdapter()
let authRetrier = AuthRetrier()

// Replace YourAPI with your main endpoint enum
let mainProvider = MoyaProvider<YourAPI>(
    plugins: [NetworkLoggerPlugin()], // Optional: for debugging requests
    adapter: authAdapter,
    retrier: authRetrier
)

Important Notes

  • Avoid Refresh Loops: Add a check in shouldRetry to skip retry logic if the request is for token refresh itself—this prevents infinite loops.
  • Concurrency Safety: The isRefreshing flag and pendingRequests array ensure only one refresh call runs at a time, even if multiple requests fail simultaneously.
  • Security: Never store tokens in UserDefaults—use Keychain for encrypted, secure storage.
  • Backend Alignment: Confirm with your backend team which status code they use for unauthorized requests (401 vs 403) and adjust the check accordingly.

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.19 04:16:36