Alamofire-安全策略

web服務器和服務器通訊的時候,使用https鏈接是很是重要的,可以對數據加密傳輸、身份認證。https協議須要到ca申請證書,部署到服務器,應用端鏈接都是對該連接受信任的。證書可申請也能夠自籤,自簽證書須要客戶端驗證經過才能訪問。php

1、HTTP協議

HTTP是互聯網的基礎協議,默認端口80,爲知足應用需求HTTP也在不斷的版本升級改進,從0.9版本1.1版本功能不斷的強大起來。HTTP演變可參考:www.ruanyifeng.com/blog/2016/0…html

HTTP的特色:nginx

  • HTTP客戶端請求只須要肯定請求方法和路徑。經常使用方法有GET、POST、HEAD,因爲對參數封裝形式不同,通常獲取數據使用GET,上傳數據使用POSTGET參數暴露在連接中,POST則封裝在請求體中
  • 使用靈活,可傳輸任意類型的數據,經過Content-Type標記傳輸的數據類型
  • HTTP協議是無狀態協議,對處理過的事務無記憶能力,爲了客戶端服務端更好的交互,引用了CookieSession來存儲請求中產生的狀態

工做流程:web

http.png

不須要任何處理,客戶端要服務端就給。swift

2、HTTPS協議

HTTPS協議爲超文本傳輸安全協議,默認端口443HTTPS=HTTP+SSL,對數據進行加密,保護數據在交互時不被竊取,提升了對服務器惡意攻擊及數據假裝的成本。使用該協議要求服務器申請證書並配置協議環境。api

HTTPS的工做流程:安全

https.png

  • http、https請求都須要創建TCP三次握手鏈接,略《Socket》
  • ca頒發的證書加密爲非對稱加密,公鑰加密,私鑰解密,私鑰加密,公鑰解密。加密原理參考《RSA加密》

須要申請證書,綁定域名,服務器中配置證書。通常我的會使用阿里的免費證書,雖然加密性通常,至少證書是受信任的。也能夠本身建立證書,自建證書可使用,但不會被信任,既然有免費的咱們就走正規路線😁。bash

分別發起http和https請求:服務器

www.yahibo.top www.yahibo.top網絡

經過Charles抓包觀察數據以下:

charles.png

  • 未加密請求直接抓取服務器響應的全部數據
  • 加密請求抓取到的是未知數據

固然開啓SSL代理仍是能夠抓到的:

ssl.png

所以在作APP的時候,爲了防止APP被抓包,咱們須要作反代理設置抓包,判斷應用代理設置或證書驗證。

3、HTTP和HTTPS主要區別

  1. HTTPS協議須要到ca申請證書,我的頒發證書是不受信任的;

  2. HTTP是超文本傳輸協議,明文傳輸,HTTPS則是在HTTP上加了一層SSL(安全套接層),對數據加密傳輸;

  3. HTTP在服務器上的默認端爲80HTTPS443端口

  4. HTTP鏈接是無狀態的,HTTPS協議等價於HTTP+SSL/TLS,可進行加密傳輸、身份認證的網絡協議。

二者的優缺點很明顯,HTTP不存在加密,明文傳輸,不安全,但傳輸速度快,HTTPS密文傳輸,傳輸中須要肯定應用端和服務端的保密性和數據完整性。

證書長什麼樣?

ca申請的證書,包括兩個文件.key文件.pem文件,通常在nginx配置文件中配置便可。.key是證書的私鑰文件,.pem爲證書文件。

4、安全策略

一、ServerTrustPolicyManager

在初始化SessionManager中能夠看到,有一個初始化類型設置了ServerTrustPolicyManager類對象,該對象就是安全策略的管理者。在實際開發中,APP可能會用到不一樣的主機地址host,根據業務不一樣須要要給不一樣的host設置不一樣的安全策略。所以管理者的實現以下:

open class ServerTrustPolicyManager {
    public let policies: [String: ServerTrustPolicy]
    public init(policies: [String: ServerTrustPolicy]) {
        self.policies = policies
    }
    open func serverTrustPolicy(forHost host: String) -> ServerTrustPolicy? {
        return policies[host]
    }
}
複製代碼
  • 初始化了一個policies字典,keyhost,值爲選擇的響應的策略
  • 初始化方法中設置安全策略及ServerTrustPolicy
  • 經過serverTrustPolicy方法經過host獲取ServerTrustPolicy對象

不要問爲何分離一個manager來管理ServerTrustPolicy,不直接使用,由於這是大廠分工要明確,責任到每一層。

一、ServerTrustPolicy

ServerTrustPolicy是一個枚舉類:

public enum ServerTrustPolicy {
    case performDefaultEvaluation(validateHost: Bool)
    case performRevokedEvaluation(validateHost: Bool, revocationFlags: CFOptionFlags)
    case pinCertificates(certificates: [SecCertificate], validateCertificateChain: Bool, validateHost: Bool)
    case pinPublicKeys(publicKeys: [SecKey], validateCertificateChain: Bool, validateHost: Bool)
    case disableEvaluation
    case customEvaluation((_ serverTrust: SecTrust, _ host: String) -> Bool)
     //代碼省略若干
}
複製代碼

以上是類型關聯,根據不一樣類型作處理。

  • performDefaultEvaluation默認策略類型,始終驗證主機證書的有效性
  • performRevokedEvaluation對吊銷過的證書作設置
  • pinCertificates驗證服務器返回的證書的正確性,參數決定是否驗證證書鏈
  • pinPublicKeys公鑰驗證
  • disableEvaluation無需驗證,無條件信任
  • customEvaluation自定義驗證,返回一個布爾值

以上方法在項目中並無默認配置,須要咱們配置使用,常常會選擇證書驗證、公鑰驗證、不作驗證模式。具體設置根據須要選擇。

二、發起請求

實踐出真知,咱們發送一個https請求看看效果。

let url = "https://www.yahibo.top/project/public/index.php?s=api/test/list"
sessionManager.request(url).response {
    (response) in
    print(response)
}
複製代碼

運行以下:

result.png

運行一切正常,可以請求到數據,好像也沒什麼區別,其實上面已經作了數據傳輸的說明了加密驗證。下面看一下抓包,開啓Charles,從新發送請求。

http請求一切正常。

https請求運行結果以下:

error.png

http可以正常請求,https確報錯,這個好像是咱們想要的結果,都尚未配置相關信息😂。在AF中便是https在抓包狀態下也是能夠獲取數據的,而Alamofire好像作了更多的處理,直接避免抓包。

該安全策略其實對自簽證書作的驗證,既然個人證書是合法的被承認的就不作配置了😂。若是是自簽證書須要對域名或證書作驗證以下設置:

let policies: [String: ServerTrustPolicy] = [
    "www.yahibo.top": .disableEvaluation
]
let sessionManager = SessionManager(serverTrustPolicyManager: ServerTrustPolicyManager(policies: policies))
return sessionManager
複製代碼
  • 不作驗證,或者作以上提到的策略

我覺得的被驗證錯誤,打臉😂。後續補充自簽證書驗證,肯定是否可以經過請求。這是一個失敗的實踐,不過仍是有收穫的。

……

5、網絡監測

分別看一下AFAlamofire的實現。

AFNetworking-AFNetworkReachabilityManager

枚舉類型,肯定網絡狀態:

typedef NS_ENUM(NSInteger, AFNetworkReachabilityStatus) {
    AFNetworkReachabilityStatusUnknown          = -1,
    AFNetworkReachabilityStatusNotReachable     = 0,
    AFNetworkReachabilityStatusReachableViaWWAN = 1,
    AFNetworkReachabilityStatusReachableViaWiFi = 2,
};
複製代碼
  • 未鏈接網絡、不可用網絡、蜂窩網絡、WiFi網絡
[[AFNetworkReachabilityManager sharedManager] setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status){
    [weakSelf setNetworkStates:status];
    switch (status) {
        case AFNetworkReachabilityStatusUnknown:
            NSLog(@"未知網絡");
            break;
        case AFNetworkReachabilityStatusNotReachable:
            NSLog(@"這是一個不可達網絡");
            break;
        case AFNetworkReachabilityStatusReachableViaWWAN:
            NSLog(@"這是一個蜂窩網絡");
            break;
        case AFNetworkReachabilityStatusReachableViaWiFi:
            NSLog(@"這是一個WiFi網絡");
            break;
        default:
            break;
    }
}];
[[AFNetworkReachabilityManager sharedManager] startMonitoring];
複製代碼

網絡監測無非就是以上幾個狀態,設置回調代碼塊,具體監測行爲由AFNetworkReachabilityManager完成。這裏就簡單看一下,下面看一下Alamofire的網絡監測,其實都是同樣的調用的底層api不變。

Alamofire-NetworkReachabilityManager

一樣提供了這麼一個類,來管理網絡監測任務。對框架的學習,最大的收穫就是可以更快一些的對源碼作分析,快速使用框架。

具體使用

枚舉類型:

public enum NetworkReachabilityStatus {
    case unknown
    case notReachable
    case reachable(ConnectionType)
}

public enum ConnectionType {
    case ethernetOrWiFi
    case wwan
}
複製代碼

使用:

let networkManager = NetworkReachabilityManager(host: "www.yahibo.top")
networkManager?.listener = { status in
    switch status {
    case .unknown:
        print("未知網絡")
        break
    case .notReachable:
        print("這是一個不可達網絡")
        break
    case .reachable(.ethernetOrWiFi):
        print("這是一個WiFi網絡")
        break
    case .reachable(.wwan):
        print("這是一個蜂窩網絡")
        break
}
networkManager?.startListening()
複製代碼
  • 注意對象須要聲明爲全局,在做用域中聲明會被銷燬

大同小異,編碼方式變的更簡潔了,這也符合swift的編碼風格。以上能夠看出在Alamofire中的網絡監測將WiFi網絡和蜂窩網絡歸爲一類。下面看一下源碼。

一、屬性

1️⃣、聲明一個閉包,在外部實現呢,網絡變化時掉用閉包傳值

public typealias Listener = (NetworkReachabilityStatus) -> Void
複製代碼

2️⃣、網絡是否可達,包括蜂窩和WiFi網絡

open var isReachable: Bool { return isReachableOnWWAN || isReachableOnEthernetOrWiFi }
複製代碼

3️⃣、蜂窩網絡是否爲可達的

open var isReachableOnWWAN: Bool { return networkReachabilityStatus == .reachable(.wwan) }
複製代碼

4️⃣、經過WiFi鏈接網絡

open var isReachableOnEthernetOrWiFi: Bool { return networkReachabilityStatus == .reachable(.ethernetOrWiFi) }
複製代碼

5️⃣、獲取網絡類型

open var networkReachabilityStatus: NetworkReachabilityStatus {
        guard let flags = self.flags else { return .unknown }
        return networkReachabilityStatusForFlags(flags)
}
複製代碼

6️⃣、設置監聽閉包在哪一個隊列中調用你,默認給主隊列

open var listenerQueue: DispatchQueue = DispatchQueue.main
複製代碼

7️⃣、定一個閉包類型的監聽屬性

open var listener: Listener?
複製代碼

初始化

public convenience init?(host: String) {
    guard let reachability = SCNetworkReachabilityCreateWithName(nil, host) else { return nil }
    self.init(reachability: reachability)
}
複製代碼
  • 傳入服務器域名或ip,傳入其餘只要不爲空也是能夠獲取到網絡類型,仍是設置爲咱們經常使用的服務地址比較好
  • 調用了系統方法來初始化一個網絡監測對象,和AF中是同樣的
  • 返回一個SCNetworkReachability對象,網絡地址或名稱的句柄 SCNetworkReachabilityRef

官方文檔

該對象能夠肯定當前主機的網絡狀態和目標地址的可達性,提供同步或異步接口,獲取當前的網絡狀態。以上初始化有調用了一個init初始化方法:

private init(reachability: SCNetworkReachability) {
    self.reachability = reachability
    // Set the previous flags to an unreserved value to represent unknown status
    self.previousFlags = SCNetworkReachabilityFlags(rawValue: 1 << 30)
}
複製代碼
  • 存儲對象,並獲取網絡可達標識
  • 到此好像就已經初始化完成

二、啓動監聽網絡

networkManager?.startListening()
內部實現:
open func startListening() -> Bool {
    var context = SCNetworkReachabilityContext(version: 0, info: nil, retain: nil, release: nil, copyDescription: nil)
    context.info = Unmanaged.passUnretained(self).toOpaque()
    let callbackEnabled = SCNetworkReachabilitySetCallback(
        reachability,
        { (_, flags, info) in
            let reachability = Unmanaged<NetworkReachabilityManager>.fromOpaque(info!).takeUnretainedValue()
            reachability.notifyListener(flags)
        },
        &context
    )
    let queueEnabled = SCNetworkReachabilitySetDispatchQueue(reachability, listenerQueue)
    listenerQueue.async {
        guard let flags = self.flags else { return }
        self.notifyListener(flags)
    }
    return callbackEnabled && queueEnabled
}
複製代碼
  • SCNetworkReachabilitySetCallback監聽網絡狀態的變化,發生改變即調用該回調方法
  • listenerQueue在開啓監聽是調用一次
  • 經過notifyListener通知Listener閉包
  • 將任務添加在主線程中向外回調
func notifyListener(_ flags: SCNetworkReachabilityFlags) {
    guard previousFlags != flags else { return }
    previousFlags = flags
    listener?(networkReachabilityStatusForFlags(flags))
}
複製代碼
  • 先判斷網絡狀態是否改變,若是沒有變化不通知,有變化記錄先前的網絡flags
  • 經過listener閉包向外傳遞當前的網絡狀態
func networkReachabilityStatusForFlags(_ flags: SCNetworkReachabilityFlags) -> NetworkReachabilityStatus {
    guard isNetworkReachable(with: flags) else { return .notReachable }
    var networkStatus: NetworkReachabilityStatus = .reachable(.ethernetOrWiFi)
#if os(iOS)
    if flags.contains(.isWWAN) { networkStatus = .reachable(.wwan) }
#endif
    return networkStatus
}
複製代碼
  • 經過flags來獲取當前的網絡狀態,首先看網絡是否爲可達網絡
  • 經過系統屬性對網絡狀態歸類
func isNetworkReachable(with flags: SCNetworkReachabilityFlags) -> Bool {
    let isReachable = flags.contains(.reachable)
    let needsConnection = flags.contains(.connectionRequired)
    let canConnectAutomatically = flags.contains(.connectionOnDemand) || flags.contains(.connectionOnTraffic)
    let canConnectWithoutUserInteraction = canConnectAutomatically && !flags.contains(.interventionRequired)
    return isReachable && (!needsConnection || canConnectWithoutUserInteraction)
}
複製代碼
  • 經過屬性組合來肯定網絡是否可達

三、關閉網絡監聽

當結束後執行deinit方法來調用stopListening方法:

open func stopListening() {
    SCNetworkReachabilitySetCallback(reachability, nil, nil)
    SCNetworkReachabilitySetDispatchQueue(reachability, nil)
}
複製代碼
  • 將回調置空,隊列置空便可

其實網絡監測並不複雜,只是對系統網絡監測類的一個封裝,設置枚舉對網絡狀態歸類,經過listener閉包向外發送網絡監測數據。使用只須要初始化設置主機地址,而後開啓監聽便可。所謂的監聽就是實現對網絡狀態監聽的回調閉包,是框架和系統底層網絡層的消息傳遞。

學習原理及對底層源碼探索是頗有必要的,可以幫助咱們快速開發,快速的解決問題。加油⛽️

相關文章
相關標籤/搜索