这里每天分享一个 iOS 的新知识,快来关注我吧
前言
在前几篇文章中,我们探讨了 URLSession 的基本使用和异步编程方法。本篇文章将重点介绍一个大家比较关心,而且面试中经常问到的问题,如何使用 URLSession 防止中间人攻击(Man-in-the-Middle Attack,简称 MITM),确保网络通信的安全性。
在阅读这篇文章之前,建议你先阅读我前几篇关于 URLSession
的文章,这样你会更容易理解这篇文章的内容。
URLSession 系列第一篇:iOS 开发者必须掌握的 URLSession 使用指南
什么是中间人攻击?
中间人攻击是一种常见的网络攻击方式,攻击者通过拦截和篡改通信双方之间的数据,获取敏感信息或注入恶意代码。为了防止中间人攻击,我们需要确保客户端与服务器之间的通信是安全的。
举个例子,我们常用的 Charles 抓包就算是一种中间人,抓到某个请求后,我们可以修改一些请求参数,重新发送给服务端(比如充值 1 元,改成 100 元),这种就是一个经典的中间人攻击。
使用 URLSession 防止中间人攻击
回到正题,要防止中间人攻击,关键是确保通信过程中使用的证书是可信的,且未被篡改。我们可以通过以下几个步骤来实现这一目标:
-
启用 HTTPS:确保所有网络请求使用 HTTPS 协议,而不是 HTTP。
-
验证服务器证书:在客户端手动验证服务器证书,确保其可信。
-
使用证书固定(Certificate Pinning):将服务器的公钥或证书固定在客户端,防止攻击者使用伪造的证书进行中间人攻击。
接下来我们分别说说这三个步骤。
启用 HTTPS
启用 HTTPS 是防止中间人攻击的基础步骤。HTTPS 使用 TLS(传输层安全协议)加密通信数据,确保数据在传输过程中不会被窃取或篡改,这里就不过多介绍了。
在 URLSession
中,默认情况下会使用 HTTPS 协议,只需确保你的服务器支持 HTTPS 并正确配置了 SSL/TLS 证书。
验证服务器证书
我们可以通过 URLSession
的委托方法 urlSession(_:didReceive:completionHandler:)
来手动验证服务器证书,让我们稍微改改 NetworkManager
这个类(这个类是前几篇文章中写的,可以先去补补之前的内容)。
另外我们还需要创建一个新的类 URLSessionDelegate
来处理代理方法,代码如下:
// 创建一个处理 URLSessionDelegate 的类
class DelegateHandler: NSObject, URLSessionDelegate {
func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust,
let serverTrust = challenge.protectionSpace.serverTrust {
let credential = URLCredential(trust: serverTrust)
completionHandler(.useCredential, credential)
} else {
completionHandler(.performDefaultHandling, nil)
}
}
}
class NetworkManager {
// 创建一个单例 NetworkManager 对象
static let shared = NetworkManager()
// 一个共享的 URLSession 对象
private let session: URLSession
// 防止其他对象创建 NetworkManager 对象,所以将 init() 方法私有化
private init() {
let configuration = URLSessionConfiguration.default
// 设置代理为 DelegateHandler 对象
self.session = URLSession(configuration: configuration, delegate: DelegateHandler(), delegateQueue: nil)
}
}
在这个代码中,urlSession(_:didReceive:completionHandler:)
方法在接收到服务器的证书认证时被调用。此时会检查服务器的证书,并自动信任它。
这里信任了所有有效的证书,所以还不能防止中间人攻击。
使用证书固定(Certificate Pinning)
为了更进一步确保安全性,我们可以使用证书固定(Certificate Pinning)。证书固定是一种将服务器的公钥或证书直接嵌入到客户端的技术,这样即使攻击者使用伪造的证书进行中间人攻击,客户端也能检测到。
简单来说就是把服务器的证书放到客户端,当有请求过来校验证书时,我们用本地存储的证书来和服务端的证书做对比,如果相同,则信任,否则拒绝。
获取服务器证书
首先,你需要获取服务器的证书并将其嵌入到客户端应用中。可以通过浏览器或命令行工具获取证书。
使用 openssl
命令获取证书,我们这里就以 jsonplaceholder.typicode.com
域名为例,在终端执行以下命令:
echo | openssl s_client -connect jsonplaceholder.typicode.com:443 -servername jsonplaceholder.typicode.com 2>/dev/null | openssl x509 > server.cer
稍微分解一下这个命令:
-
echo |:这是为了向 openssl s_client 发送一个空输入,以便它立即返回。
-
openssl s_client -connect jsonplaceholder.typicode.com:443 -servername jsonplaceholder.typicode.com:这是与指定的服务器建立 SSL 连接。
-
2>/dev/null:这是为了忽略错误信息,只获取我们需要的输出。
-
openssl x509:这是用来解析和显示证书的命令。
-
server.cer:这是将证书输出重定向到 server.cer 文件中。
最终将输出的证书内容保存到一个 server.cer
文件中。
也可以通过这个命令来查看证书的内容:
openssl s_client -connect yourserver.com:443 -showcerts
也可以直接用文本编辑器打开 server.cer
文件查看证书内容:
将证书添加到项目中
将获取的证书文件 server.cer
添加到 Xcode 项目中。
实现证书固定
最关键的步骤来了,在 DelegateHandler
中实现证书校验的逻辑:
// 创建一个处理 URLSessionDelegate 的类
class DelegateHandler: NSObject, URLSessionDelegate {
// 检查服务器证书是否是指定的证书
private func isServerTrusted(serverTrust: SecTrust) -> Bool {
guard let serverCertificate = SecTrustGetCertificateAtIndex(serverTrust, 0) else {
return false
}
let serverCertificateData = SecCertificateCopyData(serverCertificate) as Data
let certPath = Bundle.main.path(forResource: "server", ofType: "cer")!
let contents = try! String(contentsOfFile: certPath)
.replacingOccurrences(of: "-----BEGIN CERTIFICATE-----", with: "")
.replacingOccurrences(of: "-----END CERTIFICATE-----", with: "")
.replacingOccurrences(of: "\n", with: "")
let localCertificateData = Data(base64Encoded: contents)!
return serverCertificateData == localCertificateData
}
// 实现 URLSessionDelegate 的方法
func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust,
let serverTrust = challenge.protectionSpace.serverTrust {
if isServerTrusted(serverTrust: serverTrust) {
let credential = URLCredential(trust: serverTrust)
completionHandler(.useCredential, credential)
} else {
completionHandler(.cancelAuthenticationChallenge, nil)
}
} else {
completionHandler(.performDefaultHandling, nil)
}
}
}
解释一下代码,在这个示例中,定义了一个名为 DelegateHandler
的类,它实现了 URLSessionDelegate
协议,用于处理与服务器进行安全通信时的证书验证。
在 isServerTrusted(serverTrust:)
方法中,我们首先获取服务器证书,然后将其与本地存储的证书进行比较。如果两者相同,则信任服务器证书,否则拒绝连接。
在 urlSession(_:didReceive:completionHandler:)
方法中,我们检查服务器证书是否可信,如果是,则使用服务器证书创建一个 URLCredential
对象,否则取消连接。
这样写完之后,我们就实现了防止中间人攻击的能力,可以再试试,如果开启了 Charles 抓包,请求也是无法正常返回的。
总结
在这篇文章中,我们介绍了如何使用 URLSession
防止中间人攻击。通过启用 HTTPS、验证服务器证书以及使用证书固定(Certificate Pinning),我们可以显著提高网络通信的安全性,防止中间人攻击。
希望这篇文章对你有所帮助,也希望能够帮你更进一步理解中间人攻击相关的知识,在实际项目中应用这些技术能保障网络通信的安全。下一篇文章将继续探讨 URLSession 的其他高级用法,敬请期待!
这里每天分享一个 iOS 的新知识,快来关注我吧
本文同步自微信公众号 “iOS新知”,每天准时分享一个新知识,这里只是同步,想要及时学到就来关注我吧!