(iOS)如何處理不受信任的http網路憑證 (WebView,下載檔案)

jamesdouble發表於2018-04-27

本篇使用Swift 並附上官方文件

前陣子接了(公司A)一個專案,再加上要畢業了,學校各種忙碌,距離上一篇文章也有好大一段時間...,也是因為這個專案碰到了些小問題,才想來寫寫筆記。

一、起因

公司A有個只限內網用的公文系統(似乎是用java寫的網頁?),到目前為止都是單純在Windows上的小程式利用『URL Scheme』跟這個系統互相丟接資料,我則是要負責寫一個IOS App 跟此係統做一樣的事。但我是承包的,無法在公司A的內網下測試,經過一番討論,他們決定將系統做個測試用的灌在windows虛擬機器上,我在Mac上執行就等於我跟這個系統相同內網。

二、前置測試 ?️

假設各個內網ip如下:

(A)Mac:192.168.1.1

(B)Mac上的Windows虛擬機器:192.168.1.2

(C)實機Iphone:192.168.1.3

(D)系統網址:https://192.168.1.2:8888/Domain

A ping B,C -> OK ?、 B ping A,C -> OK ?

A 預覽 D -> OK ?、 B 預覽 D -> OK ?

接下來讓我們看看我要說的 C 預覽 D 的狀況

三、問題一 (UIWebView 預覽 D) ?

  • 第一種嘗試是利用WebView, 為什麼首選它呢?

    因為業主不希望在兩個App之間做過多的切換(一整個流程下來可能跳轉兩~三次),也不希望使用者在下次開啟Safari時,還停留在系統的頁面。

  • NSURLErrorDomain: -1003?

    WebView就在我LoadRequest時跳出這麼一個錯誤,看到ErrorDomain時,我也沒有多想,就直接認為應該是我DNS沒設定好之類的問題,於是又重開了虛擬機器,重啟站臺...!@#!$??

  • Safari可開啟?

    會突然使用Safari是因為我不想一直重新Build,內心想說結果一樣,沒想到,跳出來一個這樣的提示:

    Safari開啟系統

    按下"Continue"後,就顯示出系統的頁面了!

  • 不信任的的憑證

    有了這個彈窗的提醒,我才明白問題的癥結點原來是“憑證”,原來是之前IOS9的新設定:App Transport Security (ATS)

    ATS參考網址:https://developer.apple.com/library/content/documentation/General/Reference/InfoPlistKeyReference/Articles/CocoaKeys.html#//apple_ref/doc/uid/TP40009251-SW33

    基本上ATS就是為了要確保你的App更安全,會擋兩樣東西一個就是沒有Security的Http一個是不信任憑證的Https,不會無意間跑到釣魚網站之類的。

三、問題一解法?

  • 設定Info.plist

    第一個方法最簡單就是直接關掉ATS,直接在Info.plist增加以下其中一對Key Value Pair

    Info.plist
    1.Allow Arbitary Loads (NSAllowArbitrayLoads)
    (iOS)如何處理不受信任的http網路憑證 (WebView,下載檔案)
    用途是?解除所有連線的ATS限制,這裡指的所有連線包括URLRequest,URLConnection,URLSession,UIWebView....等。

    但是官方檔案裡也很表明寫了,只要Allow Arbitary Loads這個值被設為True,就沒有辦法通過上架稽核,所以我不採用此方法。


    2.Exception Domain (NSExceptionDomains)

    (iOS)如何處理不受信任的http網路憑證 (WebView,下載檔案)
    用途是?排除某個Domain使其不受ATS的限制。 個人認為這個Key比較適合拿來使用,如果你明確知道自己會在某個Domain之下的話。 這個Key我不知道會不會影響上架?

    官方文件:https://developer.apple.com/library/content/documentation/General/Reference/InfoPlistKeyReference/Articles/CocoaKeys.html#//apple_ref/doc/uid/TP40009251-SW35


  • HTTPS Server Trust Evaluation

    https://developer.apple.com/library/content/technotes/tn2232/_index.html#//apple_ref/doc/uid/DTS40012884-CH1-SECWEBVIEW

    另一種方法就是實作官方文件裡的『Trust Customization for Specific APIs』,也就是自定義某個要求的憑證檢查

    1.URLSession

    (iOS)如何處理不受信任的http網路憑證 (WebView,下載檔案)
    依照官方文件,我們需要實作 urlSession(_:task:didReceive:completionHandler:),如果憑證無法驗證,會發出一個Challenge供你判斷你要拒絕這個連線還是提供一個憑證來解決:

    • 遵守URLSessionDelegate

      let session = Foundation.URLSession(configuration: sessionConfig, delegate: self, delegateQueue: nil)
      複製程式碼
    • 處理 - 『Challenge』

      challenge : URLAuthenticationChallenge

      Challenge這個詞並不是ios或CocoaFrameWork才有的,而是網際網路中伺服器向使用者端發出的『Challenge–response authentication』,是一個用來驗證使用者或網路提供者的協議,會要求使用者回傳一些資訊,帳號、密碼、憑證...等,在下面會說說有哪些。

      https://developer.apple.com/reference/foundation/urlauthenticationchallenge

      https://zh.wikipedia.org/zh-cn/CHAP

      func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Swift.Void)
      複製程式碼

    { //1 let protectionspace = challenge.protectionSpace //2 let authMethod = protectionspace.authenticationMethod if authMethod == NSURLAuthenticationMethodServerTrust { //3 let credential = URLCredential(trust: challenge.protectionSpace.serverTrust!) //4 let input:URLSession.AuthChallengeDisposition = .useCredential //5 completionHandler(input, credential) } } ```

     1.***Challenge.protectionSpace*** : URLProtectionSpace -> 包含了這個 authentication request的host,port...等,讓你判斷該用什麼**credential(證照)**應付。
     
     2.***authenticationMethod*** : 這是我剛剛提到的,伺服器要求認證的方法,這個方法就會決定接下來要做的事,像是:NSURLAuthenticationMethodHTTPBasic:要求使用者回傳帳號跟密碼,而我們要處理的是NSURLAuthenticationMethodServerTrust,要求使用者回傳一個憑證。
     
     3.***URLCredential*** : 這個類有幾種差異滿大的Init(),主要就是看authenticationMethod來決定你要回傳的是什麼。![image.png](http://upload-images.jianshu.io/upload_images/3776017-a0fe3c2e078cbed5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)我們這次要做的是憑證的處理,所以用第二個建構。
     		
     >https://developer.apple.com/reference/foundation/urlprotectionspace/nsurlprotectionspace_authentication_methods
     
     4.URLSession.***AuthChallengeDisposition*** : 表示行為,包括要使用憑證、取消此次要求、執行預設動作...等列舉,我們要用憑證所以用.useCredential。
     
     5.利用completionHandle告知結果:(使用憑證,這個憑證)
    複製程式碼

    2.UIWebView - 迴歸正題我們要用UIWebView,但官方文件即提到無法自定義Https server trust,但我們還是要用上面的方法解決。

    (iOS)如何處理不受信任的http網路憑證 (WebView,下載檔案)
    棘手的地方是UIWebView只能單純LoadingRequest跟管理URLSession,必須繞道而行。

  • 實作UIWebViewDelegate記住失敗的Request,並且建立一個URLConnection。

  	 func webView(_ webView: UIWebView, shouldStartLoadWith request: URLRequest, navigationType: UIWebViewNavigationType) -> Bool
  {
      	LastRequest = request
     		return true
  }
  	
  	 func webView(_ webView: UIWebView, didFailLoadWithError error: Error)
  {
      	FailRequest = LastRequest
      	if(FailRequest != nil)
      	{
      		let _:NSURLConnection = NSURLConnection(request: FailRequest! , delegate: self)!
      	}
  }
  	```
  	
* 	雖然剛剛講的是URLSession,但兩個用法其實是相同的,所以我們遵守NSURLConnectionDelegate,並在最後重新Loading一次Request,這樣同個Request就可以通了。
  	
  ```swift
  	func connection(_ connection: NSURLConnection, willSendRequestFor challenge: URLAuthenticationChallenge)
  	{
      	if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust  {
          print("send credential Server Trust")
          let credential = URLCredential(trust: challenge.protectionSpace.serverTrust!)
          challenge.sender!.use(credential, for: challenge)
      }
      else{
          challenge.sender!.performDefaultHandling!(for: challenge)
      }
      	connection.cancel()
      	SystemWebView.loadRequest(FailRequest!)
  }
  ```
複製程式碼

相關文章