Damon

宏愿纵未了 奋斗总不太晚

0%

AFNetworking源码浅析

在阅读之前,希望先了解 NSURLSession 的使用和 HTTP/HTTPS 的基础知识。

AFNetworking 的结构:

  • Manager(网络通信,处理网络请求)
    • AFHTTPSessionManger 和外界交互的,本身并没有做什么事情,AFURLSessionManager 的子类
    • AFURLSessionManager 主要的实现逻辑是在这个类
  • Serialization(网络消息的序列化和反序列化)
  • Security(网络安全、https)
  • Reachability (网络状态监听)

AFHTTPSessionManger

这个的主要职责就是:

一、提供初始化的接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
+ (instancetype)manager {
return [[[self class] alloc] initWithBaseURL:nil];
}

- (instancetype)init {
return [self initWithBaseURL:nil];
}

- (instancetype)initWithBaseURL:(NSURL *)url {
return [self initWithBaseURL:url sessionConfiguration:nil];
}

- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration {
return [self initWithBaseURL:nil sessionConfiguration:configuration];
}

- (instancetype)initWithBaseURL:(NSURL *)url
sessionConfiguration:(NSURLSessionConfiguration *)configuration
{
// 具体的实现在 AFURLSessionManager
self = [super initWithSessionConfiguration:configuration];
if (!self) {
return nil;
}

// 1.
if ([[url path] length] > 0 && ![[url absoluteString] hasSuffix:@"/"]) {
url = [url URLByAppendingPathComponent:@""];
}

self.baseURL = url;
// 2.
self.requestSerializer = [AFHTTPRequestSerializer serializer];
// 3.
self.responseSerializer = [AFJSONResponseSerializer serializer];
return self;
}
  1. 域名加 / 和不加对服务器是有影响的,不加的话会有很小的损耗。
  2. 默认的请求格式是 application/x-www-form-urlencoded
  3. 默认的响应格式是 json

二、生成请求任务 NSURLSessionDataTask

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
- (NSURLSessionDataTask *)dataTaskWithHTTPMethod:(NSString *)method
URLString:(NSString *)URLString
parameters:(nullable id)parameters
headers:(nullable NSDictionary <NSString *, NSString *> *)headers
uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgress
downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgress
success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure
{

NSError *serializationError = nil;

// 1.
NSMutableURLRequest *request = [self.requestSerializer requestWithMethod:method URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters error:&serializationError];

// 2.
for (NSString *headerField in headers.keyEnumerator) {
[request setValue:headers[headerField] forHTTPHeaderField:headerField];
}

if (serializationError) {
if (failure) {
dispatch_async(self.completionQueue ?: dispatch_get_main_queue(), ^{
failure(nil, serializationError);
});
}

return nil;
}

__block NSURLSessionDataTask *dataTask = nil;

// 3.
dataTask = [self dataTaskWithRequest:request
uploadProgress:uploadProgress
downloadProgress:downloadProgress
completionHandler:^(NSURLResponse * __unused response, id responseObject, NSError *error) {
if (error) {
if (failure) {
failure(dataTask, error);
}
} else {
if (success) {
success(dataTask, responseObject);
}
}
}];

return dataTask;
}
  1. 通过 requestSerializer 来构造 一个 request
  2. 设置请求头
  3. 生成 NSURLSessionDataTask 对象,是在 AFURLSessionManager 中实现的

AFURLSessionManager

我们来看下 AFURLSessionManager 的职责:

一、初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration {
self = [super init];
if (!self) {
return nil;
}

if (!configuration) {
configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
}

self.sessionConfiguration = configuration;

// 1.
self.operationQueue = [[NSOperationQueue alloc] init];
self.operationQueue.maxConcurrentOperationCount = 1;

self.responseSerializer = [AFJSONResponseSerializer serializer];
self.securityPolicy = [AFSecurityPolicy defaultPolicy];

#if !TARGET_OS_WATCH
self.reachabilityManager = [AFNetworkReachabilityManager sharedManager];
#endif

self.mutableTaskDelegatesKeyedByTaskIdentifier = [[NSMutableDictionary alloc] init];

self.lock = [[NSLock alloc] init];
self.lock.name = AFURLSessionManagerLockName;

// 2.
[self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) {
for (NSURLSessionDataTask *task in dataTasks) {
[self addDelegateForDataTask:task uploadProgress:nil downloadProgress:nil completionHandler:nil];
}

for (NSURLSessionUploadTask *uploadTask in uploadTasks) {
[self addDelegateForUploadTask:uploadTask progress:nil completionHandler:nil];
}

for (NSURLSessionDownloadTask *downloadTask in downloadTasks) {
[self addDelegateForDownloadTask:downloadTask progress:nil destination:nil completionHandler:nil];
}
}];

return self;
}
  1. 初始化一个串行队列,用来处理网络返回的数据,这是官方规定的一定要串行。
  2. 我们可以看到 AF 创建了 session 之后获取到了所有的 task,遍历置为nil。但是我们刚创建 session,应该是不会有任何 task 的。在 issue 中找到了答案,原因是为了防止从后台进入前台,一些之前的后台请求任务而导致的 crash

二、生成 NSURLSessionDataTask

之前的 AFHTTPSessionManger 是调用 dataTaskWithRequest 方法来生成 dataTask 的,现在我们来看这个方法做了什么

1
2
3
4
5
6
7
8
9
10
11
- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request
uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgressBlock
downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgressBlock
completionHandler:(nullable void (^)(NSURLResponse *response, id _Nullable responseObject, NSError * _Nullable error))completionHandler {

NSURLSessionDataTask *dataTask = [self.session dataTaskWithRequest:request];

[self addDelegateForDataTask:dataTask uploadProgress:uploadProgressBlock downloadProgress:downloadProgressBlock completionHandler:completionHandler];

return dataTask;
}

调用了 addDelegateForDataTask ,继续跟进去

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
- (void)addDelegateForDataTask:(NSURLSessionDataTask *)dataTask
uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgressBlock
downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgressBlock
completionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))completionHandler
{
// 1.
AFURLSessionManagerTaskDelegate *delegate = [[AFURLSessionManagerTaskDelegate alloc] initWithTask:dataTask];
// 2.
delegate.manager = self;
// 3.
delegate.completionHandler = completionHandler;

dataTask.taskDescription = self.taskDescriptionForSessionTasks;
// 4.
[self setDelegate:delegate forTask:dataTask];

delegate.uploadProgressBlock = uploadProgressBlock;
delegate.downloadProgressBlock = downloadProgressBlock;
}

在这里可以看到 AFURLSessionManagerTaskDelegate ,这个类的设计很不错。在介绍这个类之前,先想一个问题,NSURLSession 发送的请求,响应结果是通过代理返回的,如果同时把 A,B,C三个请求发出去,你怎么知道返回的结果是对应这三个的哪个请求呢?AF 是通过 AFURLSessionManagerTaskDelegate 这个类来完成的。

  1. 创建 AFURLSessionManagerTaskDelegate 对象
  2. 设置 delegate 的 manager 属性
  3. 把请求回调交给 delegate
  4. 这个是最重要的一个方法,是用来绑定 delegate 和 dataTask的一一对应的关系。
1
2
3
4
5
6
7
8
9
- (void)setDelegate:(AFURLSessionManagerTaskDelegate *)delegate
forTask:(NSURLSessionTask *)task
{
[self.lock lock];
// 1.
self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)] = delegate;
[self addNotificationObserverForTask:task];
[self.lock unlock];
}
  1. 通过字典来把 dataTask 和 delegate 一一对应起来。

这样的话刚才的问题就有答案了,请求结果返回后,通过 mutableTaskDelegatesKeyedByTaskIdentifier 字典就能找到 dataTask 对应的 delegate 对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
- (void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error
{

// 1.
AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:task];

// delegate may be nil when completing a task in the background
if (delegate) {
// 2.
[delegate URLSession:session task:task didCompleteWithError:error];
// 3.
[self removeDelegateForTask:task];

}

if (self.taskDidComplete) {
self.taskDidComplete(session, task, error);
}

}
  1. 找到 task 对应的 delegate 对象
  2. 把任务转发给 delegate 对象
  3. 移除 delegate

看一下 AF 的代理主要做了什么事

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
- (void)URLSession:(__unused NSURLSession *)session
task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error
{
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"
__strong AFURLSessionManager *manager = self.manager;

__block id responseObject = nil;

__block NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
userInfo[AFNetworkingTaskDidCompleteResponseSerializerKey] = manager.responseSerializer;

// 1.
//Performance Improvement from #2672
NSData *data = nil;
if (self.mutableData) {
data = [self.mutableData copy];
//We no longer need the reference, so nil it out to gain back some memory.
self.mutableData = nil;
}

if (self.downloadFileURL) {
userInfo[AFNetworkingTaskDidCompleteAssetPathKey] = self.downloadFileURL;
} else if (data) {
userInfo[AFNetworkingTaskDidCompleteResponseDataKey] = data;
}

// 2.
if (error) {
userInfo[AFNetworkingTaskDidCompleteErrorKey] = error;

dispatch_group_async(manager.completionGroup ?: url_session_manager_completion_group(), manager.completionQueue ?: dispatch_get_main_queue(), ^{
if (self.completionHandler) {
self.completionHandler(task.response, responseObject, error);
}

dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingTaskDidCompleteNotification object:task userInfo:userInfo];
});
});
} else {
dispatch_async(url_session_manager_processing_queue(), ^{
NSError *serializationError = nil;
responseObject = [manager.responseSerializer responseObjectForResponse:task.response data:data error:&serializationError];

if (self.downloadFileURL) {
responseObject = self.downloadFileURL;
}

if (responseObject) {
userInfo[AFNetworkingTaskDidCompleteSerializedResponseKey] = responseObject;
}

if (serializationError) {
userInfo[AFNetworkingTaskDidCompleteErrorKey] = serializationError;
}

dispatch_group_async(manager.completionGroup ?: url_session_manager_completion_group(), manager.completionQueue ?: dispatch_get_main_queue(), ^{
if (self.completionHandler) {
self.completionHandler(task.response, responseObject, serializationError);
}

dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingTaskDidCompleteNotification object:task userInfo:userInfo];
});
});
});
}
#pragma clang diagnostic pop
}

代码有点多,但是主要就做3件事

  1. 这里之前是用 [NSData datawithData: self.mutableData] 的,现在用 copy 代替,好像没什么区别?最重要是 self.mutableData = nil 这一句,避免内存过高。
  2. 根据 error 参数来区别请求成功还是失败,如果失败就把 error 包装起来回调出去,如果成功就开始做异步解析等操作

AFURLSessionManager 还有一个类:_AFURLSessionTaskSwizzling ,看一下它到底做了什么

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
@implementation _AFURLSessionTaskSwizzling

+ (void)load {

if (NSClassFromString(@"NSURLSessionTask")) {

/**
WARNING: Trouble Ahead
https://github.com/AFNetworking/AFNetworking/pull/2702
*/
/**
iOS 7 and iOS 8 differ in NSURLSessionTask implementation, which makes the next bit of code a bit tricky.
Many Unit Tests have been built to validate as much of this behavior has possible.
Here is what we know:
- NSURLSessionTasks are implemented with class clusters, meaning the class you request from the API isn't actually the type of class you will get back.
- Simply referencing `[NSURLSessionTask class]` will not work. You need to ask an `NSURLSession` to actually create an object, and grab the class from there.
- On iOS 7, `localDataTask` is a `__NSCFLocalDataTask`, which inherits from `__NSCFLocalSessionTask`, which inherits from `__NSCFURLSessionTask`.
- On iOS 8, `localDataTask` is a `__NSCFLocalDataTask`, which inherits from `__NSCFLocalSessionTask`, which inherits from `NSURLSessionTask`.
- On iOS 7, `__NSCFLocalSessionTask` and `__NSCFURLSessionTask` are the only two classes that have their own implementations of `resume` and `suspend`, and `__NSCFLocalSessionTask` DOES NOT CALL SUPER. This means both classes need to be swizzled.
- On iOS 8, `NSURLSessionTask` is the only class that implements `resume` and `suspend`. This means this is the only class that needs to be swizzled.
- Because `NSURLSessionTask` is not involved in the class hierarchy for every version of iOS, its easier to add the swizzled methods to a dummy class and manage them there.

Some Assumptions:
- No implementations of `resume` or `suspend` call super. If this were to change in a future version of iOS, we'd need to handle it.
- No background task classes override `resume` or `suspend`

The current solution:
1) Grab an instance of `__NSCFLocalDataTask` by asking an instance of `NSURLSession` for a data task.
2) Grab a pointer to the original implementation of `af_resume`
3) Check to see if the current class has an implementation of resume. If so, continue to step 4.
4) Grab the super class of the current class.
5) Grab a pointer for the current class to the current implementation of `resume`.
6) Grab a pointer for the super class to the current implementation of `resume`.
7) If the current class implementation of `resume` is not equal to the super class implementation of `resume` AND the current implementation of `resume` is not equal to the original implementation of `af_resume`, THEN swizzle the methods
8) Set the current class to the super class, and repeat steps 3-8
*/
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration ephemeralSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration];
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wnonnull"
NSURLSessionDataTask *localDataTask = [session dataTaskWithURL:nil];
#pragma clang diagnostic pop
IMP originalAFResumeIMP = method_getImplementation(class_getInstanceMethod([self class], @selector(af_resume)));
Class currentClass = [localDataTask class];

while (class_getInstanceMethod(currentClass, @selector(resume))) {
Class superClass = [currentClass superclass];
IMP classResumeIMP = method_getImplementation(class_getInstanceMethod(currentClass, @selector(resume)));
IMP superclassResumeIMP = method_getImplementation(class_getInstanceMethod(superClass, @selector(resume)));
if (classResumeIMP != superclassResumeIMP &&
originalAFResumeIMP != classResumeIMP) {
[self swizzleResumeAndSuspendMethodForClass:currentClass];
}
currentClass = [currentClass superclass];
}

[localDataTask cancel];
[session finishTasksAndInvalidate];
}
}

+ (void)swizzleResumeAndSuspendMethodForClass:(Class)theClass {
Method afResumeMethod = class_getInstanceMethod(self, @selector(af_resume));
Method afSuspendMethod = class_getInstanceMethod(self, @selector(af_suspend));

if (af_addMethod(theClass, @selector(af_resume), afResumeMethod)) {
af_swizzleSelector(theClass, @selector(resume), @selector(af_resume));
}

if (af_addMethod(theClass, @selector(af_suspend), afSuspendMethod)) {
af_swizzleSelector(theClass, @selector(suspend), @selector(af_suspend));
}
}

- (void)af_resume {
NSAssert([self respondsToSelector:@selector(state)], @"Does not respond to state");
NSURLSessionTaskState state = [self state];
[self af_resume];

if (state != NSURLSessionTaskStateRunning) {
[[NSNotificationCenter defaultCenter] postNotificationName:AFNSURLSessionTaskDidResumeNotification object:self];
}
}

- (void)af_suspend {
NSAssert([self respondsToSelector:@selector(state)], @"Does not respond to state");
NSURLSessionTaskState state = [self state];
[self af_suspend];

if (state != NSURLSessionTaskStateSuspended) {
[[NSNotificationCenter defaultCenter] postNotificationName:AFNSURLSessionTaskDidSuspendNotification object:self];
}
}

@end
  • 我们先看 af_resumeaf_suspend 这两个方法的实现,看起来就是处理原有的逻辑,然后再发一个通知,这个主要是 AF 中 UIKit 拓展使用。

  • 但是 method swizzling 会有 crash 问题

  • 这是因为 NSURLSessionTask 在 iOS7 和 iOS8 的继承链不一样导致的,可以看到上面有很多注释,我把它们翻译一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
NSURLSessionTask 的实现在 iOS 7 和 iOS 8 不一样,这使得下面的代码略显棘手

构建了很多单元测试来验证这种行为是可行的

以下我们目前所知道的:

NSURLSessionTask 是通过类簇来实现的,这意味着你从 API 请求拿到的类的类型实际上并不是你想要拿的那个类型
仅仅使用 `[NSURlSessionTask class]` 并没有什么用,你需要创建一个 `NSURLSession` 对象,再通过 `NSURLSession` 对象创建 `NSURLSessionTask` 对象,这样才行。
在 iOS7,`localDatatask` (这个是通过 NSURLSession 对象创建的 NSURLSessionTask对象)的类型是 `__NSCFLocalDataTask` ,它继承 `__NSCFLocalSessionTask`,而 `__NSCFLocalSessionTask` 继承 `__NSCFURLSessionTask`
在 iOS 8, 前面和 iOS 7 的一样,后面 `__NSCFLocalSessionTask` 继承 `NSURLSewssionTask`
在 iOS 7,`__NSCFLocalSessionTask` 和 `__NSCFURLSessionTask` 是仅有的实现了 `resume` 和 `suspend` 方法的两个类。`__NSCFLocalSessionTask` 是不会调用 `super` 的实现,所以这两个类的方法都需要 `method swizzling`
在 iOS 8,只有 `NSURLSessionTask` 这个类实现了 `resume` 和 `suspend` 方法,所以只有这个类需要 `method swizzling`
因为 `NSURLSessionTask` 并不是所有的 iOS 版本都存在,所以把 `method swizzling` 方法写在 dummy class(_AFURLSessionTaskSwizzling)会更加容易管理

一些假设:

目前的 `resume` 和 `suspend` 是不会调用父类的实现的,如果在以后的 iOS 版本中修改了,我们需要处理
没有后台 task 类重写 `resume` 和 `suspend`

目前的解决方案:

其实就是在讲代码的思路 看代码就行了 也不难懂

AFSecurityPolicy

在阅读这部分内容之前最好了解 https,可以查看这里自行了解。

AF 就是用 AFSecurityPolicy 这个类来满足各种 https 认证需求。先补充一点前置知识

当你的 app 通过 URLSessionTask 发送请求时,服务器可能会回应一个或多个身份认证质询,session task 会尝试处理,如果处理不来,就会调用 session 的代理方法来处理,如果需要代理来处理认证质询,可是又没有实现对应的方法,服务器可能会拒绝这个请求,返回 401 (Forbidden)

1
2
3
- (void)URLSession:(NSURLSession *)session
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler

实现这个方法 URLSession:didreceiveChallenge:completionHandler: 是处理整个 session 范围的质询,比如 TLS 验证,一旦成功地处理了这个质询,从这个 session 创建的所有任务仍然有效。

1
2
3
4
- (void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler

这个方法是 URLSessionTaskDelegate 协议的,用来处理特殊任务的质询,比如用户名/密码的身份验证质询,每个任务都可能发出自己的质询。如果需要处理 session 范围的质询时,但上面那个方法又没有实现的话,也会调用这个方法来代替。

证书概念

操作系统存储很多顶级 CA 的证书,由于顶级的 CA 数量有限,所以相对容易管理。那其它的签发商其实是被间接信任的,比如顶级 CA 可以授权多个二级 CA,二级 CA 又可以授权多个三级 CA,发布一个数字签名需要有上一级证书的私钥签名。

根证书

根证书就是顶级 CA 自签名的证书

证书链

因为证书是从顶级 CA 开始不断向下授权签发的,当客户端访问HTTPS站点时,服务端会返回整条证书链,一直验证到根证书时,就可以确定证书是否可信

NSURLAuthenticationChallenge

两个代理方法都会提供一个 NSURLAuthenticationChallenge 对象,它提供了处理认证质询时所需的信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@interface NSURLAuthenticationChallenge : NSObject <NSSecureCoding>
// 保护空间
@property (readonly, copy) NSURLProtectionSpace *protectionSpace;
// 建议的凭证,可能为空,可能是默认的也可能是上次认证失败使用的
@property (nullable, readonly, copy) NSURLCredential *proposedCredential;
// 之前认证失败的次数
@property (readonly) NSInteger previousFailureCount;
// 最后一个认证失败的 NSURLResponse 对象
@property (nullable, readonly, copy) NSURLResponse *failureResponse;
// 最后一个认证失败的 NSError 对象
@property (nullable, readonly, copy) NSError *error;
// 质询的发送者
@property (nullable, readonly, retain) id<NSURLAuthenticationChallengeSender> sender;
@end

其中最核心的属性是 NSURLProtectionSpace *protectionSpace ,这是认证的核心,通常被称为保护空间,表示需要认证的服务器或者域,它定义了一系列的约束去告诉我们需要向服务器提供什么样的认证。通过判断authenticationMethod指定授权方式。当发送一个 HTTPS 请求时,authenticationMethod 的值会是 NSURLAuthenticationMethodServerTrust,这种类型是 App 对服务器进行认证,其他的类型都是服务器对 App 进行认证。对于这种类型,除了以下情况,其他都可以使用默认的处理方式:

  • 使用自签名证书
  • 使用 SSL Pinning 防止中间人攻击

NSURLProtectionSpace 还有一个 SecTrustRef 对象属性,SecTrustRef 对象是服务器传过来的,表示了验证证书链需要的所有内容(证书+策略)

继续看代理方法方法,completionHandler 需要回调 NSURLCredentialNSURLSessionAuthChallengeDisposition

NSURLCredential

验证凭证对象,最常用的初始化方法

1
2
// 这个 trust 就是从 challenge.protectionSpace.serverTrust 获取
- (instancetype)initWithTrust:(SecTrustRef)trust

NSURLSessionAuthChallengeDisposition

1
2
3
NSURLSessionAuthChallengeUseCredential // 使用指定的证书
NSURLSessionAuthChallengePerformDefaultHandling // 默认处理方式,会忽略证书
NSURLSessionAuthChallengeCancelAuthenticationChallenge // 取消请求,会忽略证书

现在看一下 AFSecurityPolicy 是怎么处理的,在这之前先看一下其比较重要的属性:

1
2
3
4
5
6
7
8
// https 验证模式
@property (readonly, nonatomic, assign) AFSSLPinningMode SSLPinningMode;
// 可以匹配服务端证书验证的证书,使用 SSL pinning 会用到
@property (nonatomic, strong, nullable) NSSet <NSData *> *pinnedCertificates;
// 是否支持不合法的证书,例如:自签名证书
@property (nonatomic, assign) BOOL allowInvalidCertificates;
// 是否验证证书域名
@property (nonatomic, assign) BOOL validatesDomainName;

AFSSLPinningMode 的值

1
2
3
4
5
6
7
8
typedef NS_ENUM(NSUInteger, AFSSLPinningMode) {
//不验证
AFSSLPinningModeNone,
//只验证公钥
AFSSLPinningModePublicKey,
//验证证书
AFSSLPinningModeCertificate,
};

接下去是 AF 处理 https 的流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
- (void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
{
NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
__block NSURLCredential *credential = nil;

// 1.
if (self.taskDidReceiveAuthenticationChallenge) {
disposition = self.taskDidReceiveAuthenticationChallenge(session, task, challenge, &credential);
} else {
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {

// 2.
if ([self.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) {
disposition = NSURLSessionAuthChallengeUseCredential;
credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
} else {
disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
}
} else {
disposition = NSURLSessionAuthChallengePerformDefaultHandling;
}
}

if (completionHandler) {
completionHandler(disposition, credential);
}
}
  1. 如果有自定义block的话就自己做认证处理
  2. 调用 AFSecurityPolicy 对象的 evaluateServerTrust 方法,如果验证通过就生成证书对象和使用这个证书,不通过就取消这个请求

核心是 evaluateServerTrust 这个方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust
forDomain:(NSString *)domain
{
// 1.
if (domain && self.allowInvalidCertificates && self.validatesDomainName && (self.SSLPinningMode == AFSSLPinningModeNone || [self.pinnedCertificates count] == 0)) {
// https://developer.apple.com/library/mac/documentation/NetworkingInternet/Conceptual/NetworkingTopics/Articles/OverridingSSLChainValidationCorrectly.html
// According to the docs, you should only trust your provided certs for evaluation.
// Pinned certificates are added to the trust. Without pinned certificates,
// there is nothing to evaluate against.
//
// From Apple Docs:
// "Do not implicitly trust self-signed certificates as anchors (kSecTrustOptionImplicitAnchors).
// Instead, add your own (self-signed) CA certificate to the list of trusted anchors."
NSLog(@"In order to validate a domain name for self signed certificates, you MUST use pinning.");
return NO;
}

NSMutableArray *policies = [NSMutableArray array];
// 2.
if (self.validatesDomainName) {
[policies addObject:(__bridge_transfer id)SecPolicyCreateSSL(true, (__bridge CFStringRef)domain)];
} else {
[policies addObject:(__bridge_transfer id)SecPolicyCreateBasicX509()];
}

// 3
SecTrustSetPolicies(serverTrust, (__bridge CFArrayRef)policies);

// 4.
if (self.SSLPinningMode == AFSSLPinningModeNone) {
return self.allowInvalidCertificates || AFServerTrustIsValid(serverTrust);
} else if (!AFServerTrustIsValid(serverTrust) && !self.allowInvalidCertificates) { // 5。
return NO;
}

// 6.
switch (self.SSLPinningMode) {
case AFSSLPinningModeNone:
default:
return NO;
// 7.
case AFSSLPinningModeCertificate: {
NSMutableArray *pinnedCertificates = [NSMutableArray array];
for (NSData *certificateData in self.pinnedCertificates) {
[pinnedCertificates addObject:(__bridge_transfer id)SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certificateData)];
}
SecTrustSetAnchorCertificates(serverTrust, (__bridge CFArrayRef)pinnedCertificates);

if (!AFServerTrustIsValid(serverTrust)) {
return NO;
}

// obtain the chain after being validated, which *should* contain the pinned certificate in the last position (if it's the Root CA)
NSArray *serverCertificates = AFCertificateTrustChainForServerTrust(serverTrust);

for (NSData *trustChainCertificate in [serverCertificates reverseObjectEnumerator]) {
if ([self.pinnedCertificates containsObject:trustChainCertificate]) {
return YES;
}
}

return NO;
}

// 8.
case AFSSLPinningModePublicKey: {
NSUInteger trustedPublicKeyCount = 0;
NSArray *publicKeys = AFPublicKeyTrustChainForServerTrust(serverTrust);

for (id trustChainPublicKey in publicKeys) {
for (id pinnedPublicKey in self.pinnedPublicKeys) {
if (AFSecKeyIsEqualToKey((__bridge SecKeyRef)trustChainPublicKey, (__bridge SecKeyRef)pinnedPublicKey)) {
trustedPublicKeyCount += 1;
}
}
}
return trustedPublicKeyCount > 0;
}
}

return NO;
}
  1. 首先是判断条件:如果有域名,允许自签名证书,要验证域名,那么就不能是 AFSSLPinningModeNone ,和添加的证书为0个
  2. 如果需要验证域名,就使用 SecPolicyCreateSSL 函数,如果不需要,就用 SecPolicyCreateBasicX509 函数,这两个函数都是返回 SecPolicyRef 对象,这是一个策略,用于在验证证书链时定义规则:在链中的证书要检查的内容(签名,到期日期等),SecPolicyCreateBasicX509 返回的是验证签名,到期日期等,不用验证域名,SecPolicyCreateSSL 则会多验证域名
  3. 告诉服务器客户端如何验证 serverTrust
  4. 如果 SSLPinningMode == AFSSLPinningModeNone ,这个情况可能是使用自签名,如果是允许自签名或者 AFServerTrustIsValid(serverTrust) 验证通过就返回 true
  5. 如果不允许自签名和 AFServerTrustIsValid(serverTrust) 验证不通过返回 false
  6. 判断 SSLPinningMode 的值
  7. 如果是 AFSSLPinningModeCertificate ,把 pinnedCertificates 数组的证书转成 SecCertificateRef 类型的数据,然后调用 SecTrustSetAnchorCertificates 告诉系统除了维护的证书列表还信任列表中的证书,验证的时候证书链的证书存在这个列表中,则信任该证书,调用 AFServerTrustIsValid 验证证书是否有效, 调用 AFCertificateTrustChainForServerTrust 拿到服务端的证书链,如果有和本地证书匹配的,就验证成功,否则失败
  8. AFSSLPinningModePublicKey 模式,这个也是 SSL Pinning 方式验证,只不过只验证证书里的公钥匙,不验证有效期等信息。调用 AFPublicKeyTrustChainForServerTrust 拿到服务端证书链对应的公钥,如果和本地的想匹配,验证成功,否则失败

这个方法有一些验证函数,可以看一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
static BOOL AFServerTrustIsValid(SecTrustRef serverTrust) {
BOOL isValid = NO;
SecTrustResultType result;
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
__Require_noErr_Quiet(SecTrustEvaluate(serverTrust, &result), _out);
#pragma clang diagnostic pop

isValid = (result == kSecTrustResultUnspecified || result == kSecTrustResultProceed);

_out:
return isValid;
}
  1. 调用 SecTrustEvaluate 方法来评估证书
  2. kSecTrustResultUnspecified 表示评估成功,证书被信任,但是用户没有显示信任该证书
  3. kSecTrustResultProceed 表示评估成功,用户显示信任

其它的函数有兴趣可以自行观看。AFNetworking 这部分写的逻辑有点混乱且复杂,Alamofire 的实现会更加清晰一点。