WebView如何处理不受信任的http网路凭证

WebView如何处理不受信任的http网路凭证

本篇使用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)

    用途是?解除所有连线的ATS限制,这里指的所有连线包括URLRequest,URLConnection,URLSession,UIWebView....等。
      
    

    但是官方文件里也很表明写了,只要Allow Arbitary Loads这个值被设为True,就没有办法通过上架审核,所以我不采用此方法。

    2.Exception Domain (NSExceptionDomains)

    用途是?排除某个Domain使其不受ATS的限制。
    个人认为这个Key比较适合拿来使用,如果你明确知道自己会在某个Domain之下的话。
    这个Key我不知道会不会影响上架?

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


依照官方文档,我们需要实作 urlSession(_:task:didReceive:completionHandler:),如果凭证无法验证,会发出一个Challenge供你判断你要拒绝这个连线还是提供一个凭证来解决:

  • 遵守URLSessionDelegate
    
1
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
	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来决定你要回传的是什么。 ![](https://jamesdoubleblogphoto.oss-cn-shanghai.aliyuncs.com/iOSBlog/ArticleImages/2019/15770897288708.jpg) 我们这次要做的是凭证的处理,所以用第二个建构。 >https://developer.apple.com/reference/foundation/urlprotectionspace/nsurlprotectionspace_authentication_methods 4.URLSession.***AuthChallengeDisposition*** : 表示行为,包括要使用凭证、取消此次要求、执行预设动作...等列举,我们要用凭证所以用.useCredential。 5.利用completionHandle告知结果:(使用凭证,这个凭证)

2.UIWebView - 回归正题我们要用UIWebView,但官方文档即提到无法自定义Https server trust,但我们还是要用上面的方法解决。

棘手的地方是UIWebView只能单纯LoadingRequest跟管理URLSession,必须绕道而行。

  • 实作UIWebViewDelegate记住失败的Request,并且建立一个URLConnection。
    
1
2
3
4
5
6
7
8
9
10
11
12
13
14
	 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就可以通了。
    
1
2
3
4
5
6
7
8
9
10
11
12
13
	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!)
}

Comments

Your browser is out-of-date!

Update your browser to view this website correctly.&npsb;Update my browser now

×