如何在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
shouldRetryto skip retry logic if the request is for token refresh itself—this prevents infinite loops. - Concurrency Safety: The
isRefreshingflag andpendingRequestsarray 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
相关产品推荐
相关产品推荐

