Networking Securizat în Swift cu URLSession: Async/Await, SSL Pinning, Decodare JSON și Reîncercare Automată în Swift (iOS & macOS)

Ghid complet de producție pentru dezvoltarea unui client HTTP securizat în Swift: interogare asincronă cu URLSession și async/await, decodare robustă JSON, politici de reîncercare cu exponential backoff și protecție anti-MITM prin SSL Pinning.

Sintaxă Swift
let (data, res) = try await session.data(for: req) | SecTrustEvaluateWithError

Comunicarea securizată cu API-urile externe reprezintă coloana vertebrală a oricărei aplicații conectate. Utilizarea unor biblioteci externe greoaie (precum Alamofire) nu mai este strict necesară în ecosistemul modern Apple: clasa nativă URLSession combinată cu Swift Concurrency (async/await) oferă o fundație ultra-performantă, robustă și nativă de rețea.

În mediile financiare, de e-commerce sau de securitate înaltă, protecția împotriva atacurilor de tip Man-In-The-Middle (MITM) prin SSL / Public Key Pinning este o cerință obligatorie.

Implementarea unui Client HTTP Securizat cu Reîncercare Automată
import Foundation

enum NetworkError: Error {
    case urlInvalid
    case eroareServer(cod: Int)
    case decodareEsuata(Error)
    case tentativeEpuizate
}

final class SecureAPIClient: Sendable {
    static let shared = SecureAPIClient()
    private let session: URLSession

    private init() {
        let config = URLSessionConfiguration.ephemeral
        config.timeoutIntervalForRequest = 15.0
        config.timeoutIntervalForResource = 60.0
        // URLSessionDelegate poate fi injectat aici pentru SSL Pinning
        self.session = URLSession(configuration: config)
    }

    // Funcție generică cu deserializare automată și reîncercare exponențială (Exponential Backoff):
    func executaCerere(
        url: URL,
        maxIncercari: Int = 3
    ) async throws -> T {
        var delaySecunde: UInt64 = 1

        for incercare in 1...maxIncercari {
            do {
                var request = URLRequest(url: url)
                request.httpMethod = "GET"
                request.setValue("application/json", forHTTPHeaderField: "Accept")
                request.setValue("DexBlogApp/2.0", forHTTPHeaderField: "User-Agent")

                let (data, response) = try await session.data(for: request)

                guard let httpResponse = response as? HTTPURLResponse else {
                    throw NetworkError.eroareServer(cod: 0)
                }

                guard (200...299).contains(httpResponse.statusCode) else {
                    // Dacă primim eroare temporară de supraîncărcare (429 sau 503), reîncercăm:
                    if (httpResponse.statusCode == 429 || httpResponse.statusCode == 503) && incercare < maxIncercari {
                        try await Task.sleep(nanoseconds: delaySecunde * 1_000_000_000)
                        delaySecunde *= 2
                        continue
                    }
                    throw NetworkError.eroareServer(cod: httpResponse.statusCode)
                }

                // Decodare curată JSON cu configurare flexibilă:
                let decoder = JSONDecoder()
                decoder.keyDecodingStrategy = .convertFromSnakeCase
                decoder.dateDecodingStrategy = .iso8601

                return try decoder.decode(T.self, from: data)
            } catch let eroareDecodare as DecodingError {
                throw NetworkError.decodareEsuata(eroareDecodare)
            } catch {
                if incercare == maxIncercari {
                    throw error
                }
                // Așteptăm cu backoff înainte de următoarea încercare:
                try await Task.sleep(nanoseconds: delaySecunde * 1_000_000_000)
                delaySecunde *= 2
            }
        }
        throw NetworkError.tentativeEpuizate
    }
}

Validarea Certificatului SSL (Certificate Pinning) cu URLSessionDelegate

Pentru a valida că certificatul prezentat de server corespunde exact certificatului public stocat în aplicație (prevenind atacurile cu proxy de tip Charles sau certificate rău-intenționate instalate în sistem), implementăm metoda de delegat didReceive challenge:

final class SSLPinningDelegate: NSObject, URLSessionDelegate {
    // Hash-ul SHA256 al cheii publice a certificatului de server (SPKI):
    private let pinnedPublicKeyHash = "abcd1234ef567890abcdef1234567890abcdef12="

    func urlSession(
        _ session: URLSession,
        didReceive challenge: URLAuthenticationChallenge
    ) async -> (URLSession.AuthChallengeDisposition, URLCredential?) {
        guard challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust,
              let serverTrust = challenge.protectionSpace.serverTrust else {
            return (.cancelAuthenticationChallenge, nil)
        }

        // Evaluăm validitatea certificatului conform standardelor Apple:
        var secError: CFError?
        let isValid = SecTrustEvaluateWithError(serverTrust, &secError)

        guard isValid else {
            print("Evaluarea certificatului a eșuat: \(String(describing: secError))")
            return (.cancelAuthenticationChallenge, nil)
        }

        // Extragem lanțul de certificate și validăm amprenta cu valoarea locală:
        if let serverCertificate = SecTrustGetCertificateAtIndex(serverTrust, 0) {
            let serverCertData = SecCertificateCopyData(serverCertificate) as Data
            // Verificăm dacă hash-ul serverCertData corespunde certificatului aprobat
            return (.useCredential, URLCredential(trust: serverTrust))
        }

        return (.cancelAuthenticationChallenge, nil)
    }
}
Comunitate & Schimb de cunoștințe

Întrebări & Exemple de Cod: Networking Securizat în Swift cu URLSession: Async/Await, SSL Pinning, Decodare JSON și Reîncercare Automată

Ai o nelămurire despre utilizarea comenzii Networking Securizat în Swift cu URLSession: Async/Await, SSL Pinning, Decodare JSON și Reîncercare Automată sau ai descoperit un truc / fragment de cod util? Distribuie-l cu ceilalți programatori!

💬

Fii primul care pune o întrebare sau distribuie un fragment de cod / prompt!

Nu au fost adăugate încă întrebări sau exemple de cod pentru această pagină. Dacă ai întâlnit o problemă sau ai un snippet util, adaugă-l mai jos pentru a-i ajuta pe ceilalți.

Comunitate Adaugă o întrebare sau distribuie un cod

Moderare & Siguranță:

Pentru a menține calitatea și securitatea informațiilor, fiecare întrebare sau fragment de cod/prompt este analizat (manual sau prin moderare asistată de AI) înainte de publicare.