Alamofire源码学习目录合集swift
相关文件:
ServerTrustEvaluation.swiftapi
当请求须要进行身份验证的时候,URLSessionDelegate会回调方法数组
open func urlSession(_ session: URLSession, task: URLSessionTask, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void)
复制代码
来让调用者处理验证操做,Alamofire中响应URLSessionDelegate的对象为SessionDelegate,在对应的回调方法中会先对验证类型进行判断,若是是服务器验证类型(https,自签名证书等),就使用ServerTrustEvaluation中的相关对象方法来处理验证,其余类型的验证则使用Request中的credential来处理。安全
对服务器进行校验的时候,使用的是系统的Security框架中的SecTrust,SecPolicy等对象来调用C类型的函数来操做,为了方便调用,Alamofire在ServerTrustEvaluation中对相关类型进行了不少扩展。先弄明白这些扩展的做用于原理后对接下来去学习校验流程有很大的帮助。服务器
扩展方式有两种:markdown
.af
调用返回AlamofireExtension包裹对象后再调用相关方法,好处是不会避免扩展入侵。具体的会在后面的工具扩展方法学习笔记中详细讲解。extension Array where Element == ServerTrustEvaluating {
#if os(Linux)
// Add this same convenience method for Linux.
#else
// 对须要认证的host遍历数组来认证, 任何一个处理器失败都会抛出错误
public func evaluate(_ trust: SecTrust, forHost host: String) throws {
for evaluator in self {
try evaluator.evaluate(trust, forHost: host)
}
}
#endif
}
复制代码
extension Bundle: AlamofireExtended {}
extension AlamofireExtension where ExtendedType: Bundle {
// 把bundle中全部有效的证书都读取出来返回
public var certificates: [SecCertificate] {
paths(forResourcesOfTypes: [".cer", ".CER", ".crt", ".CRT", ".der", ".DER"]).compactMap { path in
//这里用compactMap来把获取失败的证书过滤掉
guard
let certificateData = try? Data(contentsOf: URL(fileURLWithPath: path)) as CFData,
let certificate = SecCertificateCreateWithData(nil, certificateData) else { return nil }
return certificate
}
}
// 返回bundle中全部可用证书的公钥
public var publicKeys: [SecKey] {
certificates.af.publicKeys
}
// 根据扩展类型数组, 把bundle中全部这些扩展的文件路径以数组形式返回
public func paths(forResourcesOfTypes types: [String]) -> [String] {
Array(Set(types.flatMap { type.paths(forResourcesOfType: $0, inDirectory: nil) }))
}
}
复制代码
extension SecCertificate: AlamofireExtended {}
extension AlamofireExtension where ExtendedType == SecCertificate {
// 从证书中提取公钥, 若是提取失败, 返回nil
public var publicKey: SecKey? {
let policy = SecPolicyCreateBasicX509()
var trust: SecTrust?
let trustCreationStatus = SecTrustCreateWithCertificates(type, policy, &trust)
guard let createdTrust = trust, trustCreationStatus == errSecSuccess else { return nil }
return SecTrustCopyPublicKey(createdTrust)
}
}
复制代码
// MARK: 证书数组扩展
extension Array: AlamofireExtended {}
extension AlamofireExtension where ExtendedType == [SecCertificate] {
// 把数组中的证书对象所有以Data格式返回
public var data: [Data] {
type.map { SecCertificateCopyData($0) as Data }
}
// 把全部证书对象的公钥提取出来,使用compactMap过滤提取失败的对象
public var publicKeys: [SecKey] {
type.compactMap { $0.af.publicKey }
}
}
复制代码
iOS如下对SecTrust进行评估校验的方法为SecTrustEvaluate(SecTrust, SecTrustResultType *)
,该方法不会抛出错误, 校验结果使用第二个参数的SecTrustResultType指针返回, 方法返回OSStatus状态码来标记检测状态,因此Alamofire对OSStatus与SecTrustResultType进行了扩展,添加了快速断定是否成功的计算属性cookie
// MARK: OSStatus扩展
extension OSStatus: AlamofireExtended {}
extension AlamofireExtension where ExtendedType == OSStatus {
// 返回是否成功
public var isSuccess: Bool { type == errSecSuccess }
}
// MARK: SecTrustResultType扩展
extension SecTrustResultType: AlamofireExtended {}
extension AlamofireExtension where ExtendedType == SecTrustResultType {
// 返回是否成功
public var isSuccess: Bool {
(type == .unspecified || type == .proceed)
}
}
复制代码
extension SecPolicy: AlamofireExtended {}
extension AlamofireExtension where ExtendedType == SecPolicy {
// 校验服务端证书, 可是不须要主机名匹配
public static let `default` = SecPolicyCreateSSL(true, nil)
// 校验服务端证书, 同时必须匹配主机名
public static func hostname(_ hostname: String) -> SecPolicy {
SecPolicyCreateSSL(true, hostname as CFString)
}
// 校验证书是否被撤销, 建立策略失败会抛出异常
public static func revocation(options: RevocationTrustEvaluator.Options) throws -> SecPolicy {
guard let policy = SecPolicyCreateRevocation(options.rawValue) else {
throw AFError.serverTrustEvaluationFailed(reason: .revocationPolicyCreationFailed)
}
return policy
}
}
复制代码
SecTrust对象自己只是一个指针,用来进行证书校验,经过调用一些列CApi风格的方法,应用SecPolicy校验策略来怼指针所指向的待校验信息来进行校验,校验结果也存在指针数据中,也是须要经过CApi方法来获取结果或错误,iOS12开始提供了新的Api来进行校验,新的Api能够在校验失败时抛出错误,而旧的Api则须要根据状态码来自行拼装错误,所以Alamofire同时提供了iOS12以上与如下的两套校验方法,而且把旧的方法标记为iOS12 Deprecatedsession
extension SecTrust: AlamofireExtended {}
extension AlamofireExtension where ExtendedType == SecTrust {
//MARK: iOS12 以上鉴定方法
@available(iOS 12, macOS 10.14, tvOS 12, watchOS 5, *)
public func evaluate(afterApplying policy: SecPolicy) throws {
// 先应用安全策略, 而后调用evaluate方法校验
try apply(policy: policy).af.evaluate()
}
// 使用iOS12 的api来评估对指定证书和策略的信任
// 错误类型使用CFError指针返回
@available(iOS 12, macOS 10.14, tvOS 12, watchOS 5, *)
public func evaluate() throws {
var error: CFError?
// 使用iOS12以上的Api进行校验, 错误使用CFError指针返回
let evaluationSucceeded = SecTrustEvaluateWithError(type, &error)
if !evaluationSucceeded {
// 校验失败抛出错误
throw AFError.serverTrustEvaluationFailed(reason: .trustEvaluationFailed(error: error))
}
}
//MARK: iOS12 如下鉴定方法
@available(iOS, introduced: 10, deprecated: 12, renamed: "evaluate(afterApplying:)")
@available(macOS, introduced: 10.12, deprecated: 10.14, renamed: "evaluate(afterApplying:)")
@available(tvOS, introduced: 10, deprecated: 12, renamed: "evaluate(afterApplying:)")
@available(watchOS, introduced: 3, deprecated: 5, renamed: "evaluate(afterApplying:)")
public func validate(policy: SecPolicy, errorProducer: (_ status: OSStatus, _ result: SecTrustResultType) -> Error) throws {
// 一样是先应用安全策略,而后调用方法校验
try apply(policy: policy).af.validate(errorProducer: errorProducer)
}
// iOS12 如下评估证书与策略是否信任的方法, 评估结果会以SecTrustResultType指针返回, 同时评估方法会返回OSStatus值来判断结果, 当评估失败时, 使用函数入参的errorProducer来把两个状态码变成Error类型抛出
@available(iOS, introduced: 10, deprecated: 12, renamed: "evaluate()")
@available(macOS, introduced: 10.12, deprecated: 10.14, renamed: "evaluate()")
@available(tvOS, introduced: 10, deprecated: 12, renamed: "evaluate()")
@available(watchOS, introduced: 3, deprecated: 5, renamed: "evaluate()")
public func validate(errorProducer: (_ status: OSStatus, _ result: SecTrustResultType) -> Error) throws {
// 调用iOS12 如下校验方法, 获取结果与状态
var result = SecTrustResultType.invalid
let status = SecTrustEvaluate(type, &result)
// 出错的话使用传入的错误产生闭包来生成Error并抛出
guard status.af.isSuccess && result.af.isSuccess else {
throw errorProducer(status, result)
}
}
// 把安全策略应用到SecTrust上, 准备接下来的评估, 失败会抛出对应错误
public func apply(policy: SecPolicy) throws -> SecTrust {
let status = SecTrustSetPolicies(type, policy)
guard status.af.isSuccess else {
throw AFError.serverTrustEvaluationFailed(reason: .policyApplicationFailed(trust: type,
policy: policy,
status: status))
}
return type
}
//MARK: 工具扩展
// 设置自定义证书到self, 容许对自签名证书进行彻底验证
public func setAnchorCertificates(_ certificates: [SecCertificate]) throws {
// 添加证书
let status = SecTrustSetAnchorCertificates(type, certificates as CFArray)
guard status.af.isSuccess else {
throw AFError.serverTrustEvaluationFailed(reason: .settingAnchorCertificatesFailed(status: status,
certificates: certificates))
}
// 只信任设置的证书
let onlyStatus = SecTrustSetAnchorCertificatesOnly(type, true)
guard onlyStatus.af.isSuccess else {
throw AFError.serverTrustEvaluationFailed(reason: .settingAnchorCertificatesFailed(status: onlyStatus,
certificates: certificates))
}
}
// 获取公钥列表
public var publicKeys: [SecKey] {
certificates.af.publicKeys
}
// 获取持有的证书
public var certificates: [SecCertificate] {
// 这里使用了compactMap, 由于根据index遍历获取证书可能会取不到, 因此copactMap过滤后返回所有的有效证书数组
(0..<SecTrustGetCertificateCount(type)).compactMap { index in
SecTrustGetCertificateAtIndex(type, index)
}
}
// 证书的data类型
public var certificateData: [Data] {
certificates.af.data
}
// 使用默认安全策略来评估, 不对主机名进行验证
public func performDefaultValidation(forHost host: String) throws {
if #available(iOS 12, macOS 10.14, tvOS 12, watchOS 5, *) {
try evaluate(afterApplying: SecPolicy.af.default)
} else {
try validate(policy: SecPolicy.af.default) { status, result in
AFError.serverTrustEvaluationFailed(reason: .defaultEvaluationFailed(output: .init(host, type, status, result)))
}
}
}
// 使用默认安全策略来评估, 同时会进行主机名验证
public func performValidation(forHost host: String) throws {
if #available(iOS 12, macOS 10.14, tvOS 12, watchOS 5, *) {
try evaluate(afterApplying: SecPolicy.af.hostname(host))
} else {
try validate(policy: SecPolicy.af.hostname(host)) { status, result in
AFError.serverTrustEvaluationFailed(reason: .hostValidationFailed(output: .init(host, type, status, result)))
}
}
}
}
复制代码
ServerTrustManager的做用是在初始化的时候能够针对不一样的host持有不一样的校验器,而后ServerTrustManager会被Session持有,在SessionDelegate须要对服务器的校验进行处理的时候,经过SessionDelegate中的SessionStateProvider代理来从Session中获取ServerTrustManager,而后从映射中根据host取出来对应的校验器返回给SessionDelegate用来对服务器进行校验处理。闭包
open class ServerTrustManager {
/// 是否全部的域名都须要认证, 默认为true
/// 若为true,每一个host都要有对应的认证器存在,不然会抛出异常
/// 若为false,当某个host没有对应的认证器时,返回nil,不抛出错误
public let allHostsMustBeEvaluated: Bool
/// 保存host与认证器的映射
public let evaluators: [String: ServerTrustEvaluating]
/// 初始化, 因为不一样的服务区可能会有不一样的认证方式, 因此管理的认证方式是基于域名的
public init(allHostsMustBeEvaluated: Bool = true, evaluators: [String: ServerTrustEvaluating]) {
self.allHostsMustBeEvaluated = allHostsMustBeEvaluated
self.evaluators = evaluators
}
/// 根据域名返回对应的认证器, 可抛出错误
open func serverTrustEvaluator(forHost host: String) throws -> ServerTrustEvaluating? {
guard let evaluator = evaluators[host] else {
//若设置了所有域名都要被认证, 当没有对应的认证器时, 就抛出错误
if allHostsMustBeEvaluated {
throw AFError.serverTrustEvaluationFailed(reason: .noRequiredEvaluator(host: host))
}
return nil
}
return evaluator
}
}
复制代码
该协议用来对须要校验的对象SecTrust进行校验, 同时支持支持对host进行检查,协议方法很简单, 只有一个方法:app
public protocol ServerTrustEvaluating {
#if os(Linux)
//Linux下有对应的同名方法
#else
/// 对参数SecTrust与域名进行校验, 校验结果会保存在SecTrust中, 校验失败会抛出错误
func evaluate(_ trust: SecTrust, forHost host: String) throws
#endif
}
复制代码
Alamofire内部实现了6个校验器类,能够直接拿来使用,这6个类都被修饰为final,不容许继承,若是须要实现本身的校验逻辑,须要本身实现协议,来对传入的SecTrust对象进行校验处理
使用默认的安全策略来对服务器进行校验,只会简单的控制是否须要对主机名进行验证
public final class DefaultTrustEvaluator: ServerTrustEvaluating {
private let validateHost: Bool
// 初始化, 默认会对主机名进行验证
public init(validateHost: Bool = true) {
self.validateHost = validateHost
}
public func evaluate(_ trust: SecTrust, forHost host: String) throws {
//根据对主机名进行验证与否, 分别调用两个不一样的校验扩展方法
if validateHost {
try trust.af.performValidation(forHost: host)
}
try trust.af.performDefaultValidation(forHost: host)
}
}
复制代码
可使用默认安全策略进行校验的同时,检测证书是否被吊销。Alamofire进过测试发现苹果从iOS10.1才开始支持吊销证书的检测
public final class RevocationTrustEvaluator: ServerTrustEvaluating {
// 封装CFOptionFlags来建立吊销证书校验的安全策略
public struct Options: OptionSet {
/// Perform revocation checking using the CRL (Certification Revocation List) method.
public static let crl = Options(rawValue: kSecRevocationCRLMethod)
/// Consult only locally cached replies; do not use network access.
public static let networkAccessDisabled = Options(rawValue: kSecRevocationNetworkAccessDisabled)
/// Perform revocation checking using OCSP (Online Certificate Status Protocol).
public static let ocsp = Options(rawValue: kSecRevocationOCSPMethod)
/// Prefer CRL revocation checking over OCSP; by default, OCSP is preferred.
public static let preferCRL = Options(rawValue: kSecRevocationPreferCRL)
/// Require a positive response to pass the policy. If the flag is not set, revocation checking is done on a
/// "best attempt" basis, where failure to reach the server is not considered fatal.
public static let requirePositiveResponse = Options(rawValue: kSecRevocationRequirePositiveResponse)
/// Perform either OCSP or CRL checking. The checking is performed according to the method(s) specified in the
/// certificate and the value of `preferCRL`.
public static let any = Options(rawValue: kSecRevocationUseAnyAvailableMethod)
/// The raw value of the option.
public let rawValue: CFOptionFlags
/// Creates an `Options` value with the given `CFOptionFlags`.
///
/// - Parameter rawValue: The `CFOptionFlags` value to initialize with.
public init(rawValue: CFOptionFlags) {
self.rawValue = rawValue
}
}
// 是否须要进行默认安全校验, 默认true
private let performDefaultValidation: Bool
// 是否须要进行主机名验证, 默认true
private let validateHost: Bool
// 用来建立吊销证书校验安全策略的Options, 默认.any
private let options: Options
public init(performDefaultValidation: Bool = true, validateHost: Bool = true, options: Options = .any) {
self.performDefaultValidation = performDefaultValidation
self.validateHost = validateHost
self.options = options
}
// 实现协议的校验方法
public func evaluate(_ trust: SecTrust, forHost host: String) throws {
if performDefaultValidation {
// 须要进行默认校验, 调用方法先进行默认校验
try trust.af.performDefaultValidation(forHost: host)
}
if validateHost {
// 须要验证主机名
try trust.af.performValidation(forHost: host)
}
// 须要使用吊销证书校验安全策略来进行评估, iOS12上下分别用不一样方法来进行校验
if #available(iOS 12, macOS 10.14, tvOS 12, watchOS 5, *) {
try trust.af.evaluate(afterApplying: SecPolicy.af.revocation(options: options))
} else {
try trust.af.validate(policy: SecPolicy.af.revocation(options: options)) { status, result in
AFError.serverTrustEvaluationFailed(reason: .revocationCheckFailed(output: .init(host, trust, status, result), options: options))
}
}
}
}
复制代码
可使用app内置的自定义证书来对服务端证书进行校验,可用于自签名证书验证。
public final class PinnedCertificatesTrustEvaluator: ServerTrustEvaluating {
// 保存自定义证书, 默认为app中全部的有效证书
private let certificates: [SecCertificate]
// 是否把自定义证书添加到校验的锚定证书中, 用来对自签名证书进行校验, 默认false
private let acceptSelfSignedCertificates: Bool
// 是否须要进行默认校验, 默认true
private let performDefaultValidation: Bool
// 是否验证主机名, 默认true
private let validateHost: Bool
public init(certificates: [SecCertificate] = Bundle.main.af.certificates, acceptSelfSignedCertificates: Bool = false, performDefaultValidation: Bool = true, validateHost: Bool = true) {
self.certificates = certificates
self.acceptSelfSignedCertificates = acceptSelfSignedCertificates
self.performDefaultValidation = performDefaultValidation
self.validateHost = validateHost
}
public func evaluate(_ trust: SecTrust, forHost host: String) throws {
guard !certificates.isEmpty else {
// 若是自定义证书为空, 直接抛出错误
throw AFError.serverTrustEvaluationFailed(reason: .noCertificatesFound)
}
if acceptSelfSignedCertificates {
// 须要对自签名证书校验的话, 把自定义证书数组所有添加到SecTrust中
try trust.af.setAnchorCertificates(certificates)
}
if performDefaultValidation {
// 执行默认校验
try trust.af.performDefaultValidation(forHost: host)
}
if validateHost {
// 执行主机名验证
try trust.af.performValidation(forHost: host)
}
// 从校验结果中获取服务端证书
let serverCertificatesData = Set(trust.af.certificateData)
// 获取自定义证书
let pinnedCertificatesData = Set(certificates.af.data)
// 服务端证书与自定义证书是否匹配(经过判断两个集合存在交集肯定)
let pinnedCertificatesInServerData = !serverCertificatesData.isDisjoint(with: pinnedCertificatesData)
if !pinnedCertificatesInServerData {
// 不匹配则认为校验失败, 抛出错误
throw AFError.serverTrustEvaluationFailed(reason: .certificatePinningFailed(host: host,
trust: trust,
pinnedCertificates: certificates,
serverCertificates: trust.af.certificates))
}
}
}
复制代码
使用自定义公钥来校验服务端证书,须要注意的是由于没有把自定义证书加入到SecTrust的锚定证书中,因此若是用这个校验器来对自签名证书进行校验,会失败。所以若是要校验自签名证书,请用上面的自定义证书校验器
public final class PublicKeysTrustEvaluator: ServerTrustEvaluating {
// 自定义公钥数组, 默认为app全部内置证书中可用的公钥
private let keys: [SecKey]
// 是否执行默认校验, 默认true
private let performDefaultValidation: Bool
// 是否验证主机名, 默认true
private let validateHost: Bool
public init(keys: [SecKey] = Bundle.main.af.publicKeys, performDefaultValidation: Bool = true, validateHost: Bool = true) {
self.keys = keys
self.performDefaultValidation = performDefaultValidation
self.validateHost = validateHost
}
public func evaluate(_ trust: SecTrust, forHost host: String) throws {
guard !keys.isEmpty else {
// 证书为空则抛出异常
throw AFError.serverTrustEvaluationFailed(reason: .noPublicKeysFound)
}
if performDefaultValidation {
// 执行默认校验
try trust.af.performDefaultValidation(forHost: host)
}
if validateHost {
// 验证主机名
try trust.af.performValidation(forHost: host)
}
// 默认校验成功后, 检测下自定义公钥有没有在服务端证书的公钥中存在
let pinnedKeysInServerKeys: Bool = {
// 挨个遍历自定义公钥数组与服务端证书的公钥数组, 存在相同对则校验成功
for serverPublicKey in trust.af.publicKeys {
for pinnedPublicKey in keys {
if serverPublicKey == pinnedPublicKey {
return true
}
}
}
return false
}()
if !pinnedKeysInServerKeys {
// 公钥匹配事变, 抛出错误
throw AFError.serverTrustEvaluationFailed(reason: .publicKeyPinningFailed(host: host,
trust: trust,
pinnedKeys: keys,
serverKeys: trust.af.publicKeys))
}
}
}
复制代码
初始化持有一个校验器数组,校验时逐个对数组内校验器进行校验,所有成功才会校验成功,有任何一个校验器失败都会视为校验失败
public final class CompositeTrustEvaluator: ServerTrustEvaluating {
private let evaluators: [ServerTrustEvaluating]
public init(evaluators: [ServerTrustEvaluating]) {
self.evaluators = evaluators
}
public func evaluate(_ trust: SecTrust, forHost host: String) throws {
// 简单的调用校验器数组扩展的方法对持有的校验器数组挨个执行校验,任何一个失败都会抛出错误
try evaluators.evaluate(trust, forHost: host)
}
}
复制代码
校验方法为空,不会对服务端作任何校验,只是开发用,正式环境千万不要用这个校验器。
旧版本叫DisabledEvaluator,新版本更名叫DisabledTrustEvaluator
@available(*, deprecated, renamed: "DisabledTrustEvaluator", message: "DisabledEvaluator has been renamed DisabledTrustEvaluator.")
public typealias DisabledEvaluator = DisabledTrustEvaluator
public final class DisabledTrustEvaluator: ServerTrustEvaluating {
public init() {}
public func evaluate(_ trust: SecTrust, forHost host: String) throws {}
}
复制代码
以上就是Alamofire对服务端校验进行的封装,当请求碰到须要对服务端进行校验时,会经过ServerTrustManager来获取对应的校验器协议对象来对SecTrust进行校验,ServerTrustManager对校验器内部实现原理无感知,使用接口解耦后能够很自由的选择使用校验器,也能够本身实现更加复杂的校验器来进行业务逻辑处理。
HTTP请求是无状态的,所以须要用一个标记来标明某个请求属于哪一个用户,标记用户状态的方法有不少,默认的方式是在登陆后由后台建立session会话来标记用户,并下发一个cookie放在响应头中返回给请求端,请求端后续的每一个请求都会把该cookie带上,来让服务器识别该请求来自何方。可是session会话会过时,请求端须要在会话时及时刷新,来获取新的cookie或者刷新cookie的有效期。另外OAuth2还须要从单点登陆方来获取token,而且须要把token写入进请求中携带发送给服务器。对此Alamofire使用RequestInterceptor请求拦截器来封装了AuthenticationInterceptor身份验证拦截器,并使用接口抽象了验证者与验证凭证,拦截器只负责使用拦截请求,并使用验证者协议的相关方法来把验证凭证注入到请求中,在收到服务端返回的401身份验证失败时,通知验证者对验证凭证进行刷新等操做,使用方只须要关注验证者与验证凭证便可,从而不用去关心复杂的验证逻辑与刷新逻辑。
验证凭证是个须要注入要请求中的东西,注入的位置由验证者决定,验证凭证自己只须要告知验证拦截器自身是否须要刷新,当凭证过时须要刷新时,拦截器就会使用所持有的验证者来对凭证进行刷新。
public protocol AuthenticationCredential {
// 是否须要刷新凭证, 若是返回false, 下面的Authenticator接口对象将会调用刷新方法来刷新凭证
// 要注意的时, 好比凭证有效期是60min, 那么最好在过时前5分钟的时候, 就要返回true刷新凭证了, 避免后续请求中凭证过时
var requiresRefresh: Bool { get }
}
复制代码
有如下几个功能:
public protocol Authenticator: AnyObject {
// 身份验证凭据泛型
associatedtype Credential: AuthenticationCredential
// 把凭证应用到请求
// 例如OAuth2认证, 就会把凭证中的access token添加到请求头里去
func apply(_ credential: Credential, to urlRequest: inout URLRequest)
// 刷新凭证, 完成闭包是个可逃逸闭包
// 刷新方法会有两种调用状况:
// 1.当请求准备发送时, 若是凭证须要被刷新, 拦截器就会调用验证者的刷新方法来刷新凭证后再发出请求
// 2.当请求响应失败时, 拦截器会经过询问验证者是不是身份认证失败, 若是是身份认证失败, 就会调用刷新方法, 而后重试请求
// 注意, 若是是OAuth2, 就会出现分歧, 当请求收到须要验证身份时, 这个验证要求究竟是来自于内容服务器?仍是来自于验证服务器?若是是来自于内容服务器, 那么只须要验证者刷新凭证, 拦截器重试请求便可, 若是是来自于验证服务器, 那么就须要抛出错误, 让用户从新进行登陆才行.
// 使用的时候, 若是用的OAuth2, 须要跟后台小伙伴协商区分两种身份验证的状况. 拦截器会根据下一个方法来判断是否是身份验证失败了.
func refresh(_ credential: Credential, for session: Session, completion: @escaping (Result<Credential, Error>) -> Void)
// 判断请求失败是否是由于身份验证服务器致使的. 若身份验证服务器颁发的凭证不会失效, 该方法简单返回false就行.
// 若是身份验证服务器颁发的凭证会失效, 当请求碰到好比401错误时, 就要判断, 验证请求来自何方. 若来自内容服务器, 那就须要验证者刷新凭证重试请求便可, 若来自验证服务器, 就得抛出错误让用户从新登陆. 具体如何断定, 须要跟后台开发小伙伴协商
// 所以若该协议方法返回true, 拦截器就不会重试请求, 而是直接抛出错误
// 若该方法返回false, 拦截器就会根据下面的方法判断凭证是否有效, 有效的话直接重试, 无效的话会先让验证者刷新凭证后再重试
func didRequest(_ urlRequest: URLRequest, with response: HTTPURLResponse, failDueToAuthenticationError error: Error) -> Bool
// 判断当请求失败时, 本次请求有没有被当前的凭证认证过
// 若是验证服务器颁发的凭证不会失效, 该方法简单返回true就行
/* 若验证服务器颁发的凭证会失效, 就会存在这个状况: 凭证A还在有效期内, 可是已经被认证服务器标记为失效了, 那么在失效后的第一个请求响应时, 就会触发刷新逻辑, 在刷新过程当中, 还会有一系列使用凭证A认证的请求还没落地, 那么当响应触发时, 就须要根据该方法检测下本次请求是否被当前的凭证认证过. 若是认证过, 就须要暂存重试回调, 等刷新凭证后, 在执行重试回调. 若是未认证过, 表示当前持有的凭证可能已是新的凭证B了, 那么直接重试请求就好 */
func isRequest(_ urlRequest: URLRequest, authenticatedWith credential: Credential) -> Bool
}
复制代码
定义了两个错误:
public enum AuthenticationError: Error {
// 凭证丢失了
case missingCredential
// 在刷新窗口期内刷新太屡次凭证了
case excessiveRefresh
}
复制代码
使用泛型约束声明了所持有的验证者的类型
public class AuthenticationInterceptor<AuthenticatorType>: RequestInterceptor where AuthenticatorType: Authenticator // 或者这样写: public class AuthenticationInterceptor<AuthenticatorType: Authenticator>: RequestInterceptor 复制代码
定义了一些内部类型,用来处理内部处理逻辑
/// 凭证别名
public typealias Credential = AuthenticatorType.Credential
// MARK: Helper Types
// 刷新窗口, 限制指定时间段内的最大刷新次数
// 拦截器会持有每次刷新的时间戳, 每次刷新时, 经过遍历时间戳检测在最近的时间段内刷新的次数有没有超过锁限制的最大刷新次数, 超过的话, 就会取消刷新并抛出错误
public struct RefreshWindow {
// 限制周期, 默认30s
public let interval: TimeInterval
// 周期内最大刷新次数, 默认5次
public let maximumAttempts: Int
public init(interval: TimeInterval = 30.0, maximumAttempts: Int = 5) {
self.interval = interval
self.maximumAttempts = maximumAttempts
}
}
// 拦截请求准备对请求进行适配时, 若是须要刷新凭证, 就会把适配方法的参数封装成该结构体暂存在拦截器中, 刷新完成后对保存的全部结构体逐个调用completion来发送请求
private struct AdaptOperation {
let urlRequest: URLRequest
let session: Session
let completion: (Result<URLRequest, Error>) -> Void
}
// 拦截请求进行适配时的结果, 拦截器会根据不一样的适配结果执行不一样的逻辑
private enum AdaptResult {
// 适配完成, 获取到了凭证, 接下来就要让验证者来把凭证注入到请求中
case adapt(Credential)
// 验证失败, 凭证丢失或者刷新次数过多, 会取消发送请求
case doNotAdapt(AuthenticationError)
// 正在刷新凭证, 会把适配方法的参数给封装成上面的结构体暂存, 刷新凭证后会继续执行
case adaptDeferred
}
// 可变的状态, 会使用@Protected修饰保证线程安全
private struct MutableState {
// 凭证, 可能为空
var credential: Credential?
// 是否正在刷新凭证
var isRefreshing = false
// 刷新凭证的时间戳
var refreshTimestamps: [TimeInterval] = []
// 持有的刷新限制窗口
var refreshWindow: RefreshWindow?
// 暂存的适配请求的相关参数
var adaptOperations: [AdaptOperation] = []
// 暂存的重试请求的完成闭包, 当拦截器对请求失败进行重试处理时, 若是发现须要刷新凭证, 会把完成闭包暂存, 而后让验证者刷新凭证, 以后在逐个遍历该数组, 执行重试逻辑
var requestsToRetry: [(RetryResult) -> Void] = []
}
复制代码
只有4个属性,一个是计算属性,由于把几个须要线程安全的状态放在了MutableState中了
// 凭证, 直接从mutableState中线程安全的读写,
public var credential: Credential? {
get { mutableState.credential }
set { mutableState.credential = newValue }
}
// 验证者
let authenticator: AuthenticatorType
// 刷新凭证的队列
let queue = DispatchQueue(label: "org.alamofire.authentication.inspector")
// 线程安全的状态对象
@Protected
private var mutableState = MutableState()
复制代码
拦截器协议有两个方法:
// 适配请求
public func adapt(_ urlRequest: URLRequest, for session: Session, completion: @escaping (Result<URLRequest, Error>) -> Void) {
// 获取适配结果, 须要保证线程安全
let adaptResult: AdaptResult = $mutableState.write { mutableState in
// 检查下是不是已经正在刷新凭证了
guard !mutableState.isRefreshing else {
// 正在刷新凭证, 就把全部参数暂存到adaptOperations中, 而后等待刷新完成后再处理这些参数
let operation = AdaptOperation(urlRequest: urlRequest, session: session, completion: completion)
mutableState.adaptOperations.append(operation)
// 返回适配延期
return .adaptDeferred
}
// 没有再刷新凭证, 继续适配
// 获取凭证
guard let credential = mutableState.credential else {
// 凭证丢失了, 返回错误
let error = AuthenticationError.missingCredential
return .doNotAdapt(error)
}
// 检测下凭证是否有效
guard !credential.requiresRefresh else {
// 凭证过时, 须要刷新, 把参数暂存
let operation = AdaptOperation(urlRequest: urlRequest, session: session, completion: completion)
mutableState.adaptOperations.append(operation)
// 调用刷新凭证方法
refresh(credential, for: session, insideLock: &mutableState)
// 返回适配延期
return .adaptDeferred
}
// 凭证有效, 返回适配成功
return .adapt(credential)
}
// 处理适配结果
switch adaptResult {
case let .adapt(credential):
// 适配成功, 让验证者把凭证注入到请求中
var authenticatedRequest = urlRequest
authenticator.apply(credential, to: &authenticatedRequest)
// 调用完成回调, 返回适配后的请求
completion(.success(authenticatedRequest))
case let .doNotAdapt(adaptError):
// 适配失败, 调用完成回调抛出错误
completion(.failure(adaptError))
case .adaptDeferred:
// 适配延期, 不作任何处理, 等刷新凭证完成后, 会使用暂存的参数中的completion继续处理
break
}
}
// MARK: Retry
// 请求重试
public func retry(_ request: Request, for session: Session, dueTo error: Error, completion: @escaping (RetryResult) -> Void) {
// 若是没有url请求或相应, 不重试
guard let urlRequest = request.request, let response = request.response else {
completion(.doNotRetry)
return
}
// 问下验证者是不是验证服务器验证失败(OAuth2状况)
guard authenticator.didRequest(urlRequest, with: response, failDueToAuthenticationError: error) else {
// 验证服务器验证失败, 不重试, 直接返回错误, 须要用户从新登陆
completion(.doNotRetry)
return
}
// 凭证是否存在
guard let credential = credential else {
// 凭证丢失不重试
let error = AuthenticationError.missingCredential
completion(.doNotRetryWithError(error))
return
}
// 问下验证者, 请求是否被当前的凭证认证过
guard authenticator.isRequest(urlRequest, authenticatedWith: credential) else {
// 若是请求没被当前凭证认证过, 表示这个凭证是新的, 直接重试就好
completion(.retry)
return
}
// 不然表示当前凭证已经无效了, 须要刷新凭证后再重试
$mutableState.write { mutableState in
// 暂存完成回调
mutableState.requestsToRetry.append(completion)
// 若是正在刷新凭证, 返回便可
guard !mutableState.isRefreshing else { return }
// 当前没有刷新凭证, 调用refresh开始刷新
refresh(credential, for: session, insideLock: &mutableState)
}
}
复制代码
拦截器的核心方法,用来使用认证者协议对象来对凭证进行刷新,并能够:
private func refresh(_ credential: Credential, for session: Session, insideLock mutableState: inout MutableState) {
// 检测是否超出最大刷新次数
guard !isRefreshExcessive(insideLock: &mutableState) else {
// 超出最大刷新次数了, 走刷新失败逻辑
let error = AuthenticationError.excessiveRefresh
handleRefreshFailure(error, insideLock: &mutableState)
return
}
// 保存刷新时间戳
mutableState.refreshTimestamps.append(ProcessInfo.processInfo.systemUptime)
// 标记正在刷新
mutableState.isRefreshing = true
// 在队列里异步调用验证者的刷新方法, 由于拦截器在调用刷新方法前已经上锁了, 因此这里异步执行如下能够跳出锁, 能够保证刷新行为会是同步执行的.
queue.async {
self.authenticator.refresh(credential, for: session) { result in
// 刷新完成回调
self.$mutableState.write { mutableState in
switch result {
case let .success(credential):
// 成功处理
self.handleRefreshSuccess(credential, insideLock: &mutableState)
case let .failure(error):
// 失败处理
self.handleRefreshFailure(error, insideLock: &mutableState)
}
}
}
}
}
// 检测是否超出最大刷新次数
private func isRefreshExcessive(insideLock mutableState: inout MutableState) -> Bool {
// 先获取时间窗口对象, 没有的话表示没有限制最大次数
guard let refreshWindow = mutableState.refreshWindow else { return false }
// 时间窗口最小值的时间戳
let refreshWindowMin = ProcessInfo.processInfo.systemUptime - refreshWindow.interval
// 遍历保存的刷新时间戳, 使用reduce计算下窗口内刷新的次数
let refreshAttemptsWithinWindow = mutableState.refreshTimestamps.reduce(into: 0) { attempts, refreshTimestamp in
guard refreshWindowMin <= refreshTimestamp else { return }
attempts += 1
}
// 是否超过最大刷新次数了
let isRefreshExcessive = refreshAttemptsWithinWindow >= refreshWindow.maximumAttempts
return isRefreshExcessive
}
// 处理刷新成功
private func handleRefreshSuccess(_ credential: Credential, insideLock mutableState: inout MutableState) {
// 保存凭证
mutableState.credential = credential
// 取出暂存的适配请求的参数数组
let adaptOperations = mutableState.adaptOperations
// 取出暂存的重试回调数组
let requestsToRetry = mutableState.requestsToRetry
// 把self持有的暂存数据清空
mutableState.adaptOperations.removeAll()
mutableState.requestsToRetry.removeAll()
// 关闭刷新中的状态
mutableState.isRefreshing = false
// 在queue中异步执行来跳出锁
queue.async {
// 适配参数挨个继续执行请求适配逻辑
adaptOperations.forEach { self.adapt($0.urlRequest, for: $0.session, completion: $0.completion) }
// 重试block也挨个执行, 开始重试
requestsToRetry.forEach { $0(.retry) }
}
}
// 处理刷新失败
private func handleRefreshFailure(_ error: Error, insideLock mutableState: inout MutableState) {
// 取出暂存的两个数组
let adaptOperations = mutableState.adaptOperations
let requestsToRetry = mutableState.requestsToRetry
// 清空self持有的暂存数组
mutableState.adaptOperations.removeAll()
mutableState.requestsToRetry.removeAll()
// 关闭刷新中状态
mutableState.isRefreshing = false
// 在queue中异步执行来跳出锁
queue.async {
// 适配器挨个调用失败
adaptOperations.forEach { $0.completion(.failure(error)) }
// 重试器也挨个调用失败
requestsToRetry.forEach { $0(.doNotRetryWithError(error)) }
}
}
复制代码