前言
对于安全敏感的数据来说,在与服务端和Web
服务交互时使用安全的HTTPS
连接是非常重要的一步。默认情况下,Alamofire
会使用苹果安全框架内置的验证方法来验证服务端提供的证书链。虽然保证了证书链是有效的,但是也不能防止中间人攻击,为了减少中间人攻击,处理用户的敏感数据时应该使用安全策略(ServerTrustPolicy
)来做证书(certificate
)验证。
安全策略
引入安全策略ServerTrustPolicy
之前,先来个小例子🌰热热身,不然无法展开啊,先创建一个请求:
let urlString = "https://47.105.168.156:20199/users/bar"
SessionManager.default.request(urlString).response { (responseString) in
print(responseString)
}
结果呢,来看:

plist
文件中把Allow Arbitrary Loads
设置为YES
了哦);那么为什么呢? 因为https://47.105.168.156:20199/users/bar
这是一个自签名的地址。(要的就是这种效果😁😁😁)
由于所有的网络请求都会走SessionDelegate
的回调,在SessionDelegate.swift
文件中就有这样一个方法:

taskDidReceiveChallenge
存在,就说明用户自己来处理证书的事情,否则TaskDelegate
会进行任务下发,那么就来到了TaskDelegate.swift
中

let serverTrustPolicy = session.serverTrustPolicyManager?.serverTrustPolicy(forHost: host),
如果这个条件不成立,就会跳出执行,那么这个回调也就直接完成了。
如果点击serverTrustPolicyManager
跟进去会发现,它是URLSession
的一个关联属性,那么这个serverTrustPolicyManager
是在什么时候传入的呢?

既然它是URLSession
的一个关联属性,那么根据上面写的例子🌰,先从SessionManager
来看,在SessionManager
的初始化方法中,你会发现,

serverTrustPolicyManager
的SessionManager
。是的,不过,先来看一下ServerTrustPolicyManager
这个类:
open class ServerTrustPolicyManager {
// 信任策略数组
open let policies: [String: ServerTrustPolicy]
初始化方法
public init(policies: [String: ServerTrustPolicy]) {
self.policies = policies
}
// 根据主机地址返回对应的信任策略
open func serverTrustPolicy(forHost host: String) -> ServerTrustPolicy? {
return policies[host]
}
}
如果我们把ServerTrustPolicy
当做是一个安全策略,就是指对一个服务器采取的策略。那么ServerTrustPolicyManager
是对ServerTrustPolicy
的管理者。然而在现实环境中,一个APP可能会用到很多不同的主机地址。因此就产生了这样的需求,为每一个主机地址都绑定一个特定的安全策略。因此,ServerTrustPolicyManager
需要一个字典来存放这些主机地址,以及对应点的安全策略。在前面我们已经知道ServerTrustPolicyManager
是URLSession
的一个关联属性,那么它会直接绑定到URLSession
上

既然安全信任策略有多种,那么显然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
// 自定义策略,返回一个BOOL值
case customEvaluation((_ serverTrust: SecTrust, _ host: String) -> Bool)
//此处省略代码不知道多少行......
}
了解了上面这些内容,那么下面是不是可以初始化一个SessionManager
了:
let urlString = "https://47.105.168.156:20199/users/bar"
let serverTrustPolicies: [String: ServerTrustPolicy] = [
urlString: .pinCertificates(
certificates: ServerTrustPolicy.certificates(),
validateCertificateChain: true,
validateHost: true
)
]
let sessionManager = SessionManager(serverTrustPolicyManager: ServerTrustPolicyManager(policies: serverTrustPolicies))
sessionManager.request(urlString).response { (responseString) in
print(responseString)
}
这样就可以了。😏😼😼
ServerTrustPolicy
从上面⤴️⤴️⤴️我们已经大概了解了ServerTrustPolicy
这个枚举类,在它的6
个验证策略中,对于其中两个比较常用的做个了解:
pinCertificates
pinCertificates
是证书验证策略,代表客户端会将服务端返回的证书和本地保存的证书中的所有内容 全部进行校验,如果正确验证,才继续执行。
pinCertificates(certificates: [SecCertificate], validateCertificateChain: Bool, validateHost: Bool)
1️⃣参数1:certificates
代表的是证书
2️⃣参数2:validateCertificateChain
代表是否验证证书链
3️⃣参数3:validateHost
代表是否验证子地址
那么既然需要传入证书,怎么找到这个证书文件呢?
在ServerTrustPolicy
中有这样一个方法certificates
:

certificates
方法会获取到所有根目录下的证书。
在实际开发中,如果在和服务端的安全连接过程中,需要对服务端进行验证,比较好的办法就是在本地保存一些证书,接着拿到服务器传过来的证书,然后进行对比验证,如果验证成功,就表示可以信任该服务端。从上边的函数中可以看出,
Alamofire
会在Bundle
中查找带有".cer"
,".CER"
,".crt"
,".CRT"
,".der"
,".DER"
后缀的证书
pinPublicKeys
pinPublicKeys
公钥验证策略,表示客户端会将服务端返回的证书和本地保存的证书中的 PublicKey
部分 进行校验,如果验证正确,才继续执行。
case pinPublicKeys(publicKeys: [SecKey], validateCertificateChain: Bool, validateHost: Bool)
1️⃣参数1:publicKeys
表示公钥
2️⃣参数2:validateCertificateChain
代表是否验证证书链
3️⃣参数3:validateHost
代表是否验证子地址
同样的,ServerTrustPolicy
中有这样一个方法publicKeys
用来查找所有根目录下证书的公钥

这里说明一下
validateCertificateChain
验证证书链,如果服务端没有配置好证书链,那么就不能验证证书链,也验证不了,会直接取消验证。
验证流程
上面的内容已经对验证策略了解的差不多了,现在把视线拉回到最开始的时候,我们知道let serverTrustPolicy = session.serverTrustPolicyManager?.serverTrustPolicy(forHost: host),
这个条件不成立才导致验证不成功,现在改变了验证策略之后呢,代码继续走下去看看会发生什么?

evaluate
方法会去验证serverTrust
和host
:

evaluate
方法代码量有点多,但是看到switch
、case
语句就知道它是对应不同的验证策略来做不同的处理。evaluate
方法会传入两个参数一个是服务器的证书,一个是host
,结果返回一个布尔类型。
从上面的部分可以知道,正常的验证策略下,想要完成验证都要遵循三个步骤:
1️⃣:SecPolicyCreateSSL
创建策略,是否验证host
2️⃣:SecTrustSetPolicies
为待验证的对象SecTrust
设置策略
3️⃣:trustIsValid
进行验证
总结
在实际开发项目中,可能会有很多公司去购买CA
证书,在请求的时候可能不需要去验证,但对于自签证书,我们可以根据自己的开发需求进行验证,其中最安全的是证书链加host
双重验证。关于Alamofire
的安全认证策略ServerTrustPolicy
就了解到这里,如有错误,还请指正!
