在
web
服務器和服務器通信的時候,使用https
連接是非常重要的,能夠對數據加密傳輸、身份認證。https
協議需要到ca
申請證書,部署到服務器,應用端連接都是對該鏈接受信任的。證書可申請也可以自籤,自簽證書需要客戶端驗證通過才能訪問。
一、HTTP協議
HTTP
是互聯網的基礎協議,默認端口80
,爲滿足應用需求HTTP也在不斷的版本升級改進,從0.9版本
到1.1版本
功能不斷的強大起來。HTTP
演變可參考:http://www.ruanyifeng.com/blog/2016/08/http.html
HTTP的特點:
HTTP
客戶端請求只需要確定請求方法和路徑。常用方法有GET、POST、HEAD
,由於對參數封裝形式不一樣,一般獲取數據使用GET
,上傳數據使用POST
,GET
參數暴露在鏈接中,POST
則封裝在請求體中- 使用靈活,可傳輸任意類型的數據,通過
Content-Type
標記傳輸的數據類型 HTTP協議
是無狀態協議,對處理過的事務無記憶能力,爲了客戶端服務端更好的交互,引用了Cookie
和Session
來存儲請求中產生的狀態
工作流程:
不需要任何處理,客戶端要服務端就給。
二、HTTPS協議
HTTPS
協議爲超文本傳輸安全協議,默認端口443
,HTTPS=HTTP+SSL
,對數據進行加密,保護數據在交互時不被竊取,提高了對服務器惡意攻擊及數據僞裝的成本。使用該協議要求服務器申請證書並配置協議環境。
HTTPS的工作流程:
需要申請證書,綁定域名,服務器中配置證書。一般個人會使用阿里的免費證書,雖然加密性一般,至少證書是受信任的。也可以自己創建證書,自建證書可以使用,但不會被信任,既然有免費的我們就走正規路線😁。
分別發起http和https請求:
http://www.yahibo.top
https://www.yahibo.top
通過Charles抓包觀察數據如下:
- 未加密請求直接抓取服務器響應的所有數據
- 加密請求抓取到的是未知數據
當然開啓SSL代理還是可以抓到的:
因此在做APP
的時候,爲了防止APP
被抓包,我們需要做反代理設置抓包,判斷應用代理設置或證書驗證。
三、HTTP和HTTPS主要區別
-
HTTPS協議
需要到ca
申請證書,個人頒發證書是不受信任的; -
HTTP
是超文本傳輸協議,明文傳輸,HTTPS
則是在HTTP
上加了一層SSL
(安全套接層),對數據加密傳輸; -
HTTP
在服務器上的默認端爲80
,HTTPS
爲443端口
; -
HTTP
連接是無狀態的,HTTPS
協議等價於HTTP+SSL/TLS
,可進行加密傳輸、身份認證的網絡協議。
兩者的優缺點很明顯,
HTTP
不存在加密,明文傳輸,不安全,但傳輸速度快,HTTPS
密文傳輸,傳輸中需要確定應用端和服務端的保密性和數據完整性。
證書長什麼樣?
在ca
申請的證書,包括兩個文件.key文件
和.pem文件
,一般在nginx
配置文件中配置即可。.key
是證書的私鑰文件,.pem
爲證書文件。
四、安全策略
1、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
字典,key
爲host
,值爲選擇的響應的策略 - 初始化方法中設置安全策略及
ServerTrustPolicy
- 通過
serverTrustPolicy
方法通過host獲取ServerTrustPolicy
對象
不要問爲什麼分離一個manager
來管理ServerTrustPolicy
,不直接使用,因爲這是大廠分工要明確,責任到每一層。
1、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
自定義驗證,返回一個布爾值
以上方法在項目中並沒有默認配置,需要我們配置使用,經常會選擇證書驗證、公鑰驗證、不做驗證模式。具體設置根據需要選擇。
2、發起請求
實踐出真知,我們發送一個https
請求看看效果。
let url = "https://www.yahibo.top/project/public/index.php?s=api/test/list"
sessionManager.request(url).response {
(response) in
print(response)
}
運行如下:
運行一切正常,能夠請求到數據,好像也沒什麼區別,其實上面已經做了數據傳輸的說明了加密驗證。下面看一下抓包,開啓Charles
,重新發送請求。
http
請求一切正常。
https
請求運行結果如下:
http
能夠正常請求,https
確報錯,這個好像是我們想要的結果,都還沒有配置相關信息😂。在AF
中即是https
在抓包狀態下也是可以獲取數據的,而Alamofire
好像做了更多的處理,直接避免抓包。
該安全策略其實對自簽證書做的驗證,既然我的證書是合法的被認可的就不做配置了😂。如果是自簽證書需要對域名或證書做驗證如下設置:
let policies: [String: ServerTrustPolicy] = [
"www.yahibo.top": .disableEvaluation
]
let sessionManager = SessionManager(serverTrustPolicyManager: ServerTrustPolicyManager(policies: policies))
return sessionManager
- 不做驗證,或者做以上提到的策略
我以爲的被驗證錯誤,打臉😂。後續補充自簽證書驗證,確定是否能夠通過請求。這是一個失敗的實踐,不過還是有收穫的。
……
五、網絡監測
分別看一下AF
和Alamofire
的實現。
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、屬性
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)
}
- 存儲對象,並獲取網絡可達標識
- 到此好像就已經初始化完成
2、啓動監聽網絡
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)
}
- 通過屬性組合來確定網絡是否可達
3、關閉網絡監聽
當結束後執行deinit
方法來調用stopListening
方法:
open func stopListening() {
SCNetworkReachabilitySetCallback(reachability, nil, nil)
SCNetworkReachabilitySetDispatchQueue(reachability, nil)
}
- 將回調置空,隊列置空即可
其實網絡監測並不複雜,只是對系統網絡監測類的一個封裝,設置枚舉對網絡狀態歸類,通過
listener閉包
向外發送網絡監測數據。使用只需要初始化設置主機地址,然後開啓監聽即可。所謂的監聽就是實現對網絡狀態監聽的回調閉包,是框架和系統底層網絡層的消息傳遞。
學習原理及對底層源碼探索是很有必要的,能夠幫助我們快速開發,快速的解決問題。加油⛽️