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.
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)
}
}
Î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.