NSURLAuthenticationChallenge
在http请求中某些url访问需要具有权限认证,否则会返回401错误码,这时需要你在请求头中附带授权的用户名,密码;或者使用https协议第一次与服务端建立连接的时候,服务端会发送iOS客户端一个证书,这时我们需要验证服务端证书链(certificate keychain)。
当我们遇到以上情况的时候NSURLSession(在iOS7以后网络请求全部由NSURLSession来完成所以在此以NSURLSession为例)中的一个delegate会被调用:
- (void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
这个代理就是为了处理从服务端传回的NSURLAuthenticationChallenge(认证),在NSURLAuthenticationChallenge的protectionSpace中存放了服务端返回的认证信息,我们需要根据这些信息来创建对应的NSURLCredential(证书/凭证),并且设置对应的NSURLSessionAuthChallengeDisposition(处理方式)来告诉NSURLSession来如何处理处服务端返回的个认证.
在NSURLSessionAuthChallengeDisposition中包含三种类型
NSURLSessionAuthChallengePerformDefaultHandling:默认方式处理
NSURLSessionAuthChallengeUseCredential:使用指定的证书
NSURLSessionAuthChallengeCancelAuthenticationChallenge:取消
AFNetworking在代理中做了如下处理:
NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
__block NSURLCredential *credential = nil;
if (self.sessionDidReceiveAuthenticationChallenge)
{ // 自定义认证处理方式
disposition = self.sessionDidReceiveAuthenticationChallenge(session, challenge, &credential);
}
else
{ // 默认处理方式
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust])
{
// 根据AFSecurityPolicy的默认规则判断是否需要新建证书
if ([self.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host])
{
credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]; // 根据服务器返回的challenge中的protectionSpace中的SecTrustRefwww创建证书
if (credential) {
disposition = NSURLSessionAuthChallengeUseCredential; // 使用创建的证书
} else {
disposition = NSURLSessionAuthChallengePerformDefaultHandling; //使用默认方式
}
}
else
{
disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
}
}
else
{
disposition = NSURLSessionAuthChallengePerformDefaultHandling;
}
}
if (completionHandler) {
completionHandler(disposition, credential);
}
NSURLAuthenticationChallenge 是由服务端返回的权限认证类,中间包含如下信息:
- (NSURLProtectionSpace *)protectionSpace; // 这个函数返回一个类NSURLProtectionSpace,类中描述服务器中希望的认证方式以及协议,主机端口号等信息。
- (NSURLCredential *)proposedCredential; // 建议使用的证书
- (NSInteger)previousFailureCount; // 用户密码输入失败的次数。
- (NSURLResponse *)failureResponse; // 授权失败的响应头的详细信息
- (NSError *)error; // 最后一次授权失败的错误信息
image.png
当客户端发出一个网络请求的时候(图中的第1,2步),服务端会收到来自客户端的请求并作出响应(图中的第3步),这里服务端发现此请求需要输入用户名才能继续,所以客户端返回了一个权限认证,这是客户端中的NSURLSession的delegate会被调用,这时我们可以从challenge中的protectionSpace里的authenticationMethod属性获取到当前服务器需要我们提供什么类型的认证 (在图中authenticationMethod为NSURLAuthenticationMethodHTTPDigest) ,根据不同的类型使用不同的方式创建需要处理的credential然后设置对应的disposition参数,然后通过completionHandler告诉Session如何处理此次的权限认证(其实最后最后session会将此凭证返回给服务端,再由服务端去校验输入用户名密码是否正确,如果正确便继续请求,如果不正确的话会继续返回认证).
示例代码
- (IBAction)URLAuthenticationChallenge:(id)sender {
NSURL *url = [NSURL URLWithString:@"https://kyfw.12306.cn/otn/leftTicket/init"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[NSOperationQueue mainQueue]];
NSURLSessionDataTask *task = [session dataTaskWithRequest:request];
[task resume];
}
#pragma mark - NSURLSessionDataDelegate
// 只要访问的是HTTPS的路径就会调用
// 该方法的作用就是处理服务器返回的证书, 需要在该方法中告诉系统是否需要安装服务器返回的证书
// NSURLAuthenticationChallenge : 授权质问
//+ 受保护空间
//+ 服务器返回的证书类型
- (void)URLSession:(NSURLSession *)session
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
{
NSLog(@"++++++++++didReceiveChallenge");
NSLog(@"____authenticationMethod______%@", challenge.protectionSpace.authenticationMethod);
// 1.从服务器返回的受保护空间中拿到证书的类型
// 2.判断服务器返回的证书是否是服务器信任的
//AFNetworking中的处理方式
NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
__block NSURLCredential *credential = nil;
//判断服务器返回的证书是否是服务器信任的
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
NSLog(@"------->>>>>>是服务器信任的证书");
// 3.根据服务器返回的受保护空间创建一个证书
//void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential *)
//代理方法的completionHandler block接收两个参数:
//第一个参数: 代表如何处理证书
//第二个参数: 代表需要处理哪个证书
//创建证书
credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
/*
disposition:如何处理证书
NSURLSessionAuthChallengePerformDefaultHandling:默认方式处理
NSURLSessionAuthChallengeUseCredential:使用指定的证书 NSURLSessionAuthChallengeCancelAuthenticationChallenge:取消请求
*/
if (credential) {
disposition = NSURLSessionAuthChallengeUseCredential;
} else {
disposition = NSURLSessionAuthChallengePerformDefaultHandling;
}
} else {
disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
}
//安装证书
if (completionHandler) {
completionHandler(disposition, credential);
}
}
// 接收到服务器的响应
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler
{
NSLog(@"+++++++++++didReceiveResponse");
completionHandler(NSURLSessionResponseAllow);
}
// 接收到服务器返回的数据
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data
{
//NSString * str = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
//NSData *receiveData = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
//NSString *str = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(@"----------->>>>>didReceiveData--%lu-", (unsigned long)data.length);
}
// 请求完毕
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
{
NSLog(@"++++++++++++++didCompleteWithError=---%@", error);
}
打印结果如下:
2018-09-27 16:35:40.062008+0800 MOBFrameWorkTest[51074:1969808] ++++++++++didReceiveChallenge
2018-09-27 16:35:40.062133+0800 MOBFrameWorkTest[51074:1969808] ____authenticationMethod______NSURLAuthenticationMethodServerTrust
2018-09-27 16:35:40.062248+0800 MOBFrameWorkTest[51074:1969808] ------->>>>>>是服务器信任的证书
2018-09-27 16:35:40.382606+0800 MOBFrameWorkTest[51074:1969808] +++++++++++didReceiveResponse
2018-09-27 16:35:40.382938+0800 MOBFrameWorkTest[51074:1969808] ----------->>>>>didReceiveData--62096-
2018-09-27 16:35:40.383392+0800 MOBFrameWorkTest[51074:1969808] ++++++++++++++didCompleteWithError=---(null)
NSURLProtectionSpace
NSURLProtectionSpace对象表示需要身份验证的服务器或服务器的一部分。 保护空间定义了一系列匹配约束,用于确定应提供哪个凭证。
这个类没有指定的初始化程序; 它的init方法总是返回nil。 你必须通过调用Creating a protection space描述的初始化方法之一来初始化此类
//Creating a protection space
- (instancetype)initWithHost:(NSString *)host port:(NSInteger)port protocol:(NSString *)protocol realm:(NSString *)realm authenticationMethod:(NSString *)authenticationMethod
初始化一个保护空间对象,参数说明如下:
image.png
- (instancetype)initWithProxyHost:(NSString *)host port:(NSInteger)port type:(NSString *)type realm:(NSString *)realm authenticationMethod:(NSString *)authenticationMethod
初始化代理服务器的保护空间对象。
type:代理服务器的类型。 proxyType的值应设置为NSURLProtectionSpace Proxy Types(见后面)中指定的值之一。
//Getting protection space properties
@property(readonly, copy) NSString *authenticationMethod
调用者的认证方法;
@property(readonly, copy) NSArray<NSData *> *distinguishedNames
可接受的进行客户端证书认证的证书颁发机构。
如果保护空间的身份验证方法不是客户端证书,则为nil。 返回的颁发机构使用DER编码规则(DER)进行编码。
@property(readonly, copy) NSString *host
保护空间的主机名;
@property(readonly) BOOL isProxy
保护空间是否代表一个代理服务器;
@property(readonly) NSInteger port
保护空间的端口;
@property(readonly, copy) NSString *protocol
保护空间的协议;如果是代理服务器的保护空间,那么为nil;
@property(readonly, copy) NSString *proxyType
保护空间的代理类型;如果保护空间不是表示代理服务器,则为nil;
@property(readonly, copy) NSString *realm
保护空间的认证领域;
如果没有设置域,则为nil。 通常只有HTTP和HTTPS身份验证时才使用此属性。
@property(readonly) BOOL receivesCredentialSecurely
一个布尔值,指示是否可以安全地发送保护空间的凭据。
@property(readonly) SecTrustRef serverTrust
表示服务器的SSL事务状态。
如果保护空间的身份验证方法不是服务器信任的,则为nil。
NSURLProtectionSpace Protocol Types
这些常量描述了保护空间支持的协议;你可以在protocol属性找到这些值;
- NSString *const NSURLProtectionSpaceHTT:HTTP
- NSString *const NSURLProtectionSpaceHTTPS:HTTPS
- NSString *const NSURLProtectionSpaceFTP: FTP
NSURLProtectionSpace Proxy Types
这些常量描述了代理的类型;你可以在proxyType属性找到这些值;
- NSString *const NSURLProtectionSpaceHTTPProxy:HTTP代理
- NSString *const NSURLProtectionSpaceHTTPSProxy:HTTPS代理
- NSString *const NSURLProtectionSpaceFTPProxy:FTP代理
- NSString *const NSURLProtectionSpaceSOCKSProxy:SOCKS代理
NSURLProtectionSpace Authentication Methods
这些常量描述了在初始化方法中可以用的认证方法,阐述如下:
NSURLAuthenticationMethodDefault : 对协议使用默认身份验证方法
NSURLAuthenticationMethodHTTPBasic:对保护空间使用HTTP基本身份验证,如果是HTTP请求,则此方法等同于NSURLAuthenticationMethodDefault
NSURLAuthenticationMethodHTTPDigest : 对保护空间使用HTTP摘要身份验证
NSURLAuthenticationMethodHTMLForm : 对保护空间使用HTML表单身份验证
NSURLAuthenticationMethodNegotiate : 对保护空间使用Kerberos或NTLM身份验证
NSURLAuthenticationMethodNTLM : 对保护空间使用NTLM身份验证
NSURLAuthenticationMethodClientCertificate : 对保护空间使用客户端证书验证,该认证方法可以应用于任何协议。
NSURLAuthenticationMethodServerTrust : 对保护空间执行服务器信任身份验证(证书验证)。此验证方法可以应用于任何协议,最常用于覆盖SSL和TLS链验证。
NSURLAuthenticationMethodHTMLForm:URL加载系统从不会根据此认证方法发出认证挑战
网友评论