双向认证分为两方向的认证:
- 客户端认证服务端;
- 服务端认证客户端;
以下逐个说明:
认证服务器
认证服务器分为:
- DefaultTrustEvaluator,使用默认的验证方式,验证证书的有效性,证书信任链那套
- RevocationTrustEvaluator,验证证书是否被吊销
- PinnedCertificatesTrustEvaluator,验证证书是否同本地的一致,可以是自签证书
- PublicKeysTrustEvaluator,验证证书的公钥,可以是自签证书,不过这个有个好处就是不用关心证书的有效期了
- CompositeTrustEvaluator,混合模式
- DisabledTrustEvaluator,不验证证书
我们目前使用的是验证公钥,以此为例:
// 验证服务器的公钥
// 默认获取了 cer 结尾的文件,只需要把证书放到项目里边就行了,不需要显示指定
let trustManager = ServerTrustManager(evaluators: [
M_NodeServerUrl: PublicKeysTrustEvaluator()
])
let session = Session(serverTrustManager: trustManager)
// 请求的时候直接使用
session.request(xxxx)...
认证客户端
认证客户端是通过 nsurl 协议来完成的,这部分其实不完全算是 Alamofire 的行为,可以通过继承 SessionDelegate 类,然后重写方法
func urlSession(_ session: URLSession, task: URLSessionTask, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) 来实现。
具体代码如下:
class WSSessionDelegate: SessionDelegate {
override func urlSession(_ session: URLSession, task: URLSessionTask, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
switch challenge.protectionSpace.authenticationMethod {
case NSURLAuthenticationMethodClientCertificate:
// 获取证书
guard let file = Bundle.main.path(forResource: "client", ofType: "p12"),
let p12Data = try? Data.init(contentsOf: URL.init(string: file)!) else {
completionHandler(.performDefaultHandling, nil)
return
}
let p12Contents = PKCS12(pkcs12Data: p12Data, password: "xxxx")
guard let identity = p12Contents.identity else {
completionHandler(.performDefaultHandling, nil)
return
}
let credential = URLCredential(identity: identity,
certificates: nil,
persistence: .none)
challenge.sender?.use(credential, for: challenge)
completionHandler(.useCredential, credential)
default:
completionHandler(.performDefaultHandling, nil)
}
}
private class PKCS12 {
let label: String?
let keyID: NSData?
let trust: SecTrust?
let certChain: [SecTrust]?
let identity: SecIdentity?
/// Creates a PKCS12 instance from a piece of data.
/// - Parameters:
/// - pkcs12Data: the actual data we want to parse.
/// - password: The password required to unlock the PKCS12 data.
public init(pkcs12Data: Data, password: String) {
let importPasswordOption: NSDictionary
= [kSecImportExportPassphrase as NSString: password]
var items: CFArray?
let secError: OSStatus
= SecPKCS12Import(pkcs12Data as NSData,
importPasswordOption, &items)
guard secError == errSecSuccess else {
if secError == errSecAuthFailed {
NSLog("Incorrect password?")
}
fatalError("Error trying to import PKCS12 data")
}
guard let theItemsCFArray = items else { fatalError() }
let theItemsNSArray: NSArray = theItemsCFArray as NSArray
guard let dictArray
= theItemsNSArray as? [[String: AnyObject]] else {
fatalError()
}
func f<T>(key: CFString) -> T? {
for dict in dictArray {
if let value = dict[key as String] as? T {
return value
}
}
return nil
}
self.label = f(key: kSecImportItemLabel)
self.keyID = f(key: kSecImportItemKeyID)
self.trust = f(key: kSecImportItemTrust)
self.certChain = f(key: kSecImportItemCertChain)
self.identity = f(key: kSecImportItemIdentity)
}
}
}
let session = Session(delegate: WSSessionDelegate())
// 请求的时候直接使用
session.request(xxxx)...
两个结合起来可以是
let session = Session(delegate: WSSessionDelegate(), serverTrustManager: trustManager)