import Foundation import OSLog /// Token管理器 /// 负责管理应用的认证令牌,包括验证、刷新和过期处理 class TokenManager { /// 单例实例 static let shared = TokenManager() private let logger = Logger(subsystem: "com.yourapp.tokenmanager", category: "TokenManager") /// token有效期阈值(秒),在token即将过期前进行刷新 private let tokenValidityThreshold: TimeInterval = 300 /// 私有化初始化方法,确保单例模式 private init() {} // MARK: - Token 状态检查 /// 检查是否存在有效的访问令牌 var hasToken: Bool { let hasToken = KeychainHelper.getAccessToken()?.isEmpty == false logger.debug("检查token存在状态: \(hasToken ? "存在" : "不存在")") return hasToken } // MARK: - Token 验证 /// 验证并刷新token(如果需要) func validateAndRefreshTokenIfNeeded(completion: @escaping (Bool, Error?) -> Void) { logger.debug("开始验证token状态...") // 1. 检查token是否存在 guard let token = KeychainHelper.getAccessToken(), !token.isEmpty else { let error = NSError( domain: "TokenManager", code: 401, userInfo: [NSLocalizedDescriptionKey: "未找到访问令牌"] ) logger.error("❌ Token验证失败: 未找到访问令牌") completion(false, error) return } // 2. 检查token是否有效 if isTokenValid(token) { logger.debug("✅ Token验证通过,无需刷新") completion(true, nil) return } logger.debug("🔄 Token需要刷新,开始刷新流程...") // 3. token无效或即将过期,尝试刷新 refreshToken { [weak self] success, error in if success { self?.logger.debug("✅ Token刷新成功") completion(true, nil) } else { let finalError = error ?? NSError( domain: "TokenManager", code: 401, userInfo: [NSLocalizedDescriptionKey: "Token刷新失败"] ) self?.logger.error("❌ Token刷新失败: \(finalError.localizedDescription)") completion(false, finalError) } } } /// 检查token是否有效 public func isTokenValid(_ token: String) -> Bool { logger.debug("开始验证token有效性...") // 1. 基础验证:检查token是否为空 guard !token.isEmpty else { logger.error("❌ Token为空") return false } // 2. 检查token是否过期 if let expiryDate = getTokenExpiryDate(token) { logger.debug("Token过期时间: \(expiryDate)") if Date() > expiryDate { logger.error("❌ Token已过期") return false } // 检查是否需要刷新(在过期前5分钟) let timeRemaining = expiryDate.timeIntervalSinceNow logger.debug("Token剩余有效时间: \(Int(timeRemaining))秒") if timeRemaining < tokenValidityThreshold { logger.debug("⚠️ Token即将过期,需要刷新") return false } } // 3. 验证token有效性 let semaphore = DispatchSemaphore(value: 0) var isValid = false var requestCompleted = false let validationRequest = createValidationRequest(token: token) logger.debug("发送Token验证请求: \(validationRequest.url?.absoluteString ?? "未知URL")") logger.debug("请求头: \(validationRequest.allHTTPHeaderFields ?? [:])") let startTime = Date() let task = URLSession.shared.dataTask(with: validationRequest) { data, response, error in defer { requestCompleted = true semaphore.signal() } let responseTime = String(format: "%.2f秒", Date().timeIntervalSince(startTime)) // 检查网络错误 if let error = error { self.logger.error("❌ Token验证请求错误: \(error.localizedDescription) (耗时: \(responseTime))") return } // 检查响应状态码 guard let httpResponse = response as? HTTPURLResponse else { self.logger.error("❌ 无效的服务器响应 (耗时: \(responseTime))") return } let statusCode = httpResponse.statusCode self.logger.debug("收到Token验证响应 - 状态码: \(statusCode) (耗时: \(responseTime))") // 检查状态码 guard (200...299).contains(statusCode) else { self.logger.error("❌ 服务器返回错误状态码: \(statusCode)") return } // 检查响应数据 if let data = data, !data.isEmpty { do { if let json = try JSONSerialization.jsonObject(with: data) as? [String: Any] { self.logger.debug("Token验证响应数据: \(json)") } let response = try JSONDecoder().decode(IdentityCheckResponse.self, from: data) isValid = response.isValid self.logger.debug("✅ Token验证\(isValid ? "成功" : "失败")") if let userId = response.userId { self.logger.debug("用户ID: \(userId)") } if let expiresAt = response.expiresAt { self.logger.debug("Token过期时间: \(expiresAt)") } } catch { self.logger.error("❌ 解析响应数据失败: \(error.localizedDescription)") // 如果解析失败但状态码是200,我们假设token是有效的 isValid = true self.logger.debug("ℹ️ 状态码200,假设token有效") } } else { self.logger.debug("ℹ️ 没有返回数据,但状态码为200,假设token有效") isValid = true } } task.resume() // 设置超时时间(15秒) let timeoutResult = semaphore.wait(timeout: .now() + 15) // 检查是否超时 if !requestCompleted && timeoutResult == .timedOut { logger.error("⚠️ Token验证请求超时") task.cancel() return false } return isValid } /// 创建验证请求 private func createValidationRequest(token: String) -> URLRequest { let url = URL(string: APIConfig.baseURL + "/iam/identity-check")! var request = URLRequest(url: url) request.httpMethod = "POST" request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization") request.setValue("application/json", forHTTPHeaderField: "Accept") return request } /// 从token中提取过期时间 private func getTokenExpiryDate(_ token: String) -> Date? { logger.debug("开始解析token过期时间...") let parts = token.components(separatedBy: ".") guard parts.count > 1 else { logger.error("❌ 无效的token格式") return nil } guard let payloadData = base64UrlDecode(parts[1]) else { logger.error("❌ 无法解码token payload") return nil } do { if let payload = try JSONSerialization.jsonObject(with: payloadData) as? [String: Any], let exp = payload["exp"] as? TimeInterval { let expiryDate = Date(timeIntervalSince1970: exp) logger.debug("✅ 成功解析token过期时间: \(expiryDate)") return expiryDate } } catch { logger.error("❌ 解析token过期时间失败: \(error.localizedDescription)") } return nil } private func base64UrlDecode(_ base64Url: String) -> Data? { var base64 = base64Url .replacingOccurrences(of: "-", with: "+") .replacingOccurrences(of: "_", with: "/") // 添加必要的填充 let length = Double(base64.lengthOfBytes(using: .utf8)) let requiredLength = 4 * ceil(length / 4.0) let paddingLength = requiredLength - length if paddingLength > 0 { let padding = "".padding(toLength: Int(paddingLength), withPad: "=", startingAt: 0) base64 = base64 + padding } return Data(base64Encoded: base64) } /// 刷新token func refreshToken(completion: @escaping (Bool, Error?) -> Void) { logger.debug("开始刷新token...") // 获取刷新令牌 guard let refreshToken = KeychainHelper.getRefreshToken(), !refreshToken.isEmpty else { let error = NSError( domain: "TokenManager", code: 401, userInfo: [NSLocalizedDescriptionKey: "未找到刷新令牌"] ) logger.error("❌ 刷新token失败: 未找到刷新令牌") completion(false, error) return } logger.debug("找到刷新令牌,准备请求...") // 准备刷新请求参数 let parameters: [String: Any] = [ "refresh_token": refreshToken, "grant_type": "refresh_token" ] let url = APIConfig.baseURL + "/v1/iam/access-token-refresh" logger.debug("发送刷新token请求到: \(url)") logger.debug("请求参数: \(parameters)") let startTime = Date() // 发送刷新请求 NetworkService.shared.post(path: "/v1/iam/access-token-refresh", parameters: parameters) { (result: Result) in let responseTime = String(format: "%.2f秒", Date().timeIntervalSince(startTime)) switch result { case .success(let tokenResponse): self.logger.debug("✅ Token刷新成功 (耗时: \(responseTime))") self.logger.debug("新的access_token: \(tokenResponse.accessToken.prefix(10))...") if let newRefreshToken = tokenResponse.refreshToken { self.logger.debug("新的refresh_token: \(newRefreshToken.prefix(10))...") KeychainHelper.saveRefreshToken(newRefreshToken) } if let expiresIn = tokenResponse.expiresIn { self.logger.debug("Token有效期: \(expiresIn)秒") } // 保存新的访问令牌 KeychainHelper.saveAccessToken(tokenResponse.accessToken) completion(true, nil) case .failure(let error): self.logger.error("❌ Token刷新失败 (耗时: \(responseTime)): \(error.localizedDescription)") // 刷新失败,清除本地token,需要用户重新登录 self.logger.debug("清除所有token...") KeychainHelper.clearTokens() completion(false, error) } } } /// 清除所有存储的 token func clearTokens() { logger.debug("开始清除所有token...") // 清除Keychain中的token KeychainHelper.clearTokens() // 清除UserDefaults中的token相关信息 UserDefaults.standard.removeObject(forKey: "tokenExpiryDate") UserDefaults.standard.synchronize() logger.debug("✅ 所有token已清除") // 发送登出通知 NotificationCenter.default.post(name: .userDidLogout, object: nil) logger.debug("已发送登出通知") } } // MARK: - 响应模型 private struct TokenResponse: Codable { let accessToken: String let refreshToken: String? let expiresIn: TimeInterval? let tokenType: String? enum CodingKeys: String, CodingKey { case accessToken = "access_token" case refreshToken = "refresh_token" case expiresIn = "expires_in" case tokenType = "token_type" } } private struct IdentityCheckResponse: Codable { let isValid: Bool let userId: String? let expiresAt: Date? enum CodingKeys: String, CodingKey { case isValid = "is_valid" case userId = "user_id" case expiresAt = "expires_at" } } // MARK: - 通知名称 extension Notification.Name { static let userDidLogout = Notification.Name("UserDidLogoutNotification") }