热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

CFNetwork编程指南(四)——与身份验证HTTP服务器通信(CommunicatingwithAuthenticatingHTTPServers)

本文描述了如何利用CFHTTPAuthenticationAPI与需要身份验证的HTTP服务器通信。它解释了如何找到匹配的验证对象和证书,并将它们应用到HTTP请求&

本文描述了如何利用CFHTTPAuthentication API与需要身份验证的HTTP服务器通信。它解释了如何找到匹配的验证对象和证书,并将它们应用到HTTP请求,然后存储以供以后使用。

一般来说,如果一个eHTTP服务器返回一个401或407响应你的HTTP请求,这表明服务器进行身份验证需要证书。在CFHTTPAuthentication API中,每个证书组存储在CFHTTPAuthentication 对象中。因此,每个不同的身份认证服务器和每个不同用户连接的服务器需要一个单独的CFHTTPAuthentication 对象。与服务器通信,你需要应用CFHTTPAuthentication 对象到HTTP请求。接下来更加详细的解释这些步骤。

处理身份验证

添加身份验证支持将允许你的应用和身份验证服务器(如果服务器返回401或407响应)进行交互。尽管HTTP身份验证不是一个难的概念,它是一个复杂的过程。步骤如下:

1.客户端向服务器发送一个HTTP请求。

2.服务器返回一个验证给客户端。

3.客户端将原始请求的证书打包并发送给服务器。

4.在客户端和服务器之间谈判

5.当服务器验证了客户端身份,返回请求的响应。

执行这个过程需要多个步骤。整个过程如图4-1和4-2.


图4-1 处理身份验证



图4-2 找到一个身份验证对象

当一个HTTP请求返回一个401或407响应,第一步是为客户端找到一个有效的CFHTTPAuthentication 对象。一个身份验证对象包括证书和其他信息,当应用到HTTP消息请求,与服务器验证你的身份。如果你已经与服务器进行过身份验证,你会有一个有效的身份验证对象。然而,在大多数情况下,你需要使用CFHTTPAuthenticationCreateFromResponse 函数来创建一个对象。见列表4-1.

注意:所有关于身份验证的示例代码改编自ImageClient 应用。

列表4-1 创建一个身份验证对象

if (!authentication) { CFHTTPMessageRef responseHeader =(CFHTTPMessageRef) CFReadStreamCopyProperty(readStream,kCFStreamPropertyHTTPResponseHeader);// Get the authentication information from the response.
authentication =
CFHTTPAuthenticationCreateFromResponse(NULL, responseHeader);
CFRelease(responseHeader);
}

如果新身份验证对象有效,那么你已经完成可以继续图4-1的第二步。如果身份验证对象无效,然后扔掉身份验证对象和证书,检查证书。关于证书的更多信息,阅读安全证书(Security Credentials)。

不好的证书意味着服务器不接受登陆信息,它将继续监听新的证书。然而,如果证书是好的,但服务器仍然拒绝你的请求,然后服务器拒绝与你通信,你必须放弃。加上证书是不好的,重试整个过程,先创建身份验证对象直到你得到有效的证书和有效的验证对象。这个过程类似于列表4-2中的代码。

列表4-2 查找一个有效的身份验证对象

CFStreamError err;if (!authentication) { // the newly created authentication object is bad, must return
return;
} else if (!CFHTTPAuthenticationIsValid(authentication, &err)) {
// destroy authentication and credentials
if (credentials) {CFRelease(credentials);credentials = NULL;
}
CFRelease(authentication);
authentication = NULL;// check for bad credentials (to be treated separately)
if (err.domain == kCFStreamErrorDomainHTTP &&(err.error == kCFStreamErrorHTTPAuthenticationBadUserName|| err.error == kCFStreamErrorHTTPAuthenticationBadPassword))
{retryAuthorizationFailure(&authentication);return;
} else {errorOccurredLoadingImage(err);
}
}

现在你有一个有效的身份验证对象,继续图4-1中的流程。首先,考虑你是否需要证书。如果你不需要,则应由身份验证对象到HTTP请求。身份验证对象应用到HTTP请求详见列表4-4(resumeWithCredentials)。

未存储证书(在内存中保存证书(Keeping Credentials in Memory )和在永久性仓库中存储证书(Keeping Credentials in a Persistent Store)中有解释),获取有效证书的唯一方法是提示用户。大多数情况下,证书需要用户名和密码。通过传递身份验证对象到CFHTTPAuthenticationRequiresUserNameAndPassword 函数,你可以看到用户名和密码是必须的。如果证书需要用户名和密码,提示用户输入用户名和密码并在证书字典里存储。对于一个NTLM服务器,证书还需要一个域。在你有新的证书后,你可以调用列表4-4的函数resumeWithCredentials ,应用身份验证对象到HTTP请求。整个过程见列表4-3。

注意:在代码列表中,前面有省略号的注释表明这个功能超出了本文的范围,但是需要实现。这不同与正常的注释描述正在发生什么功能。

列表4-3 查找证书(如果需要)并应用它们

// ...continued from Listing 4-2
else { cancelLoad(); if (credentials) {resumeWithCredentials();
}
// are a user name & password needed?
else if (CFHTTPAuthenticationRequiresUserNameAndPassword(authentication)){CFStringRef realm = NULL;CFURLRef url = CFHTTPMessageCopyRequestURL(request);// check if you need an account domain so you can display it if necessaryif (!CFHTTPAuthenticationRequiresAccountDomain(authentication)) {realm = CFHTTPAuthenticationCopyRealm(authentication);}// ...prompt user for user name (user), password (pass)// and if necessary domain (domain) to give to the server...// Guarantee valuesif (!user) user = CFSTR("");if (!pass) pass = CFSTR("");CFDictionarySetValue(credentials,
kCFHTTPAuthenticationUsername, user); CFDictionarySetValue(credentials,
kCFHTTPAuthenticationPassword, pass);// Is an account domain needed? (used currently for NTLM only)if (CFHTTPAuthenticationRequiresAccountDomain(authentication)) {if (!domain) domain = CFSTR("");CFDictionarySetValue(credentials,kCFHTTPAuthenticationAccountDomain, domain);}if (realm) CFRelease(realm);CFRelease(url);
}
else {resumeWithCredentials();}
}

列表4-4 应用身份验证对象到请求

void resumeWithCredentials() { // Apply whatever credentials we've built up to the old request
if (!CFHTTPMessageApplyCredentialDictionary(request, authentication,credentials, NULL)) {errorOccurredLoadingImage();
} else {// Now that we've updated our request, retry the loadloadRequest();
}
}

在内存中存储证书

如果你打算经常与一个身份验证服务器进行通信,重用证书可以来避免多次提示用户服务器用户名和密码。本章解释了一次性使用身份验证代码(例如处理身份验证(Handling Authentication))需要作出的变更,在内存中存储证书以便重用。

重用证书,你的代码中需要更改三个数据结构。

1.创建一个可变的数组来保存所有的身份验证对象。

CFMutableArrayRef authArray;

代替:

CFHTTPAuthenticationRef authentication;

2.使用字典,创建身份验证对象到证书的映射。

CFMutableDictionaryRef credentialsDict;

代替:

CFMutableDictionaryRef credentials;

3.保持这些结构在你原来修改当前身份验证对象和当前证书的地方。

CFDictionaryRemoveValue(credentialsDict, authentication);

代替:

CFRelease(credentials);

现在,创建HTTP请求后,在每次加载前,查找一个匹配的身份验证对象。查找适合对象的一个简单的非优化方法见列表4-5.

列表4-5 查找一个匹配的身份验证对象

CFHTTPAuthenticationRef findAuthenticationForRequest { int i, c = CFArrayGetCount(authArray);
for (i = 0; i CFHTTPAuthenticationRef auth = (CFHTTPAuthenticationRef)CFArrayGetValueAtIndex(authArray, i);if (CFHTTPAuthenticationAppliesToRequest(auth, request)) {return auth;}
}
return NULL;
}

如果身份验证数组有一个匹配的身份验证对象,然后检查证书仓库是否有正确的证书可用。这样做可以防止你需要再次提示用户输入用户名和密码。调用CFDictionaryGetValue 函数可以查找证书,如列表4-6所示。

列表4-6 搜索证书仓库

credentials = CFDictionaryGetValue(credentialsDict, authentication);

然后应用你的匹配的身份验证对象和证书到你原始的HTTP请求并重新发送。

警告:在接收到服务器验证前,不要应用证书到HTTP请求。在你上次认证后,服务器可能改变,你可能会有一个安全风险。

有了这些变更,你的应用可以在内存中存储身份验证对象和证书以便未来使用。

在永久性仓库中存储证书

在内存中存储证书可以防止用户在特定应用启动时重新输入服务器用户名和密码。然而,当应用退出,这些证书被释放。为了避免丢失证书,将它们保存到永久性仓库,这样每个服务器证书只需要生成一次。推荐用钥匙链来存储证书。即使你有很多个钥匙链,本文档中的钥匙链指的是用户默认的钥匙链。使用钥匙链表明你存储的身份验证信息可以用于其他试图访问同一个服务器的应用中,反之亦然。

在钥匙链中存储和检索证书需要两个函数:一个用于查找证书字典用于身份验证,另一个保存最近请求的证书。本文中这些函数声明如下:

CFMutableDictionaryRef findCredentialsForAuthentication(
CFHTTPAuthenticationRef auth);
void saveCredentialsForRequest(void);

findCredentialsForAuthentication 函数首先检查内存中的证书字典本地缓存是否有证书。如何实现见列表4-6。

如果内存中没有证书的缓存,然后搜索钥匙链。使用SecKeychainFindInternetPassword函数搜索钥匙链。该函数需要大量的参数。参数和一段简短的描述HTTP身份验证证书如何使用它们,如下:

keychainOrArray

NULL 指定用户默认钥匙链列表。

serverNameLength

serverName的长度,通常是strlen(serverName)``。

serverName

从HTTP请求解析到的服务器名称

securityDomainLength

安全域的长度,或0表示没有域。在示例代码中, realm ? strlen(realm) : 0向账户传递两种情形。

securityDomain

利用CFHTTPAuthenticationCopyRealm 函数获取身份验证对象范围

accountNameLength

accountName的长度。由于accountNameNULL,值为0

accountName

当读取钥匙链记录时没有账户名,该字段为NULL

pathLength

path的长度,如果没有路径则为0.在示例代码中,path ? strlen(path) : 0向账户传递两种情形。

path

利用CFURLCopyPath 函数从身份验证对象获取路径。

port

利用CFURLGetPortNumber函数获取端口号。

protocol

代表协议类型的字符串,例如HTTP或HTTPS。通过CFURLCopyScheme 函数获取协议类型。

authenticationType

利用CFHTTPAuthenticationCopyMethod函数获取身份验证类型。

passwordLength

0,因为在读取钥匙链记录时不需要密码。

passwordData

NULL,因为在读取钥匙链记录时不需要密码。

itemRef

查找到正确的钥匙链记录,返回钥匙链记录引用对象SecKeychainItemRef

当正确的调用,代码如列表4-7所示。

列表4-7 搜索钥匙链

didFind = SecKeychainFindInternetPassword(NULL,strlen(host), host,realm ? strlen(realm) : 0, realm,0, NULL,path ? strlen(path) : 0, path,port,protocolType,authenticationType,0, NULL,&itemRef);

假设SecKeychainFindInternetPassword 成功返回,创建一个包含单独钥匙链属性(SecKeychainAttribute)的钥匙链属性列表(SecKeychainAttributeList)。钥匙链实现列表将包含用户名和密码。为了加载钥匙链属性列表,调用SecKeychainItemCopyContent 函数并将SecKeychainFindInternetPassword返回的钥匙链记录引用对象(itemRef)传递给它。该函数将用账号的用户名和密码void \**填充到钥匙链属性中。

用户名和密码可以用来创建一组新证书。列表4-8展示了这个过程。

列表4-8 从钥匙链价值服务器证书。

if (didFind == noErr) {SecKeychainAttribute attr;
SecKeychainAttributeList attrList;
UInt32 length;
void *outData;// To set the account name attribute
attr.tag = kSecAccountItemAttr;
attr.length = 0;
attr.data = NULL;attrList.count = 1;
attrList.attr = &attr;if (SecKeychainItemCopyContent(itemRef, NULL, &attrList,
&length, &outData)== noErr) {// attr.data is the account (username) and outdata is the passwordCFStringRef username =CFStringCreateWithBytes(kCFAllocatorDefault, attr.data,attr.length, kCFStringEncodingUTF8, false);CFStringRef password =CFStringCreateWithBytes(kCFAllocatorDefault, outData, length,kCFStringEncodingUTF8, false);SecKeychainItemFreeContent(&attrList, outData);// create credentials dictionary and fill it with the user name & passwordcredentials =CFDictionaryCreateMutable(NULL, 0,&kCFTypeDictionaryKeyCallBacks,&kCFTypeDictionaryValueCallBacks);CFDictionarySetValue(credentials, kCFHTTPAuthenticationUsername,username);CFDictionarySetValue(credentials, kCFHTTPAuthenticationPassword,password);CFRelease(username);CFRelease(password);
}
CFRelease(itemRef);
}

如果你可以先存储证书到钥匙链中,从钥匙链中检索证书才有用。首先,查看证书是否已经存储在钥匙链中。调用SecKeychainFindInternetPassword,传递用户名到accountName ,传递accountName 的长度到accountNameLength``。

如果记录存在,修改它来改变密码。设置钥匙链属性的数据字段包含用户名,主要你可以修改正确的属性。然后调用SecKeychainItemModifyContent 函数并传递钥匙链记录引用对象(itemRef),钥匙链属性列表和新密码。通过修改钥匙链记录而非重写,钥匙链记录会正确的更新其他相关数据也将保留。记录如列表4-9所示。

列表4-9 修改钥匙链记录

// Set the attribute to the account name
attr.tag = kSecAccountItemAttr;
attr.length = strlen(username);
attr.data = (void*)username;
// Modify the keychain entry
SecKeychainItemModifyContent(itemRef, &attrList, strlen(password),
(void *)password);

如果记录不存在,你将需要从头开始创建它。SecKeychainAddInternetPassword 函数完成该任务。它的参数与SecKeychainFindInternetPassword相同,但与调用SecKeychainFindInternetPassword相比,你提供用户名和密码给SecKeychainAddInternetPassword 。释放钥匙链记录引用对象成功后调用SecKeychainAddInternetPassword ,除非你需要在其他地方使用。见列表4-10函数调用。

列表4-10 存储一个新的钥匙链记录

SecKeychainAddInternetPassword(NULL, strlen(host), host,realm ? strlen(realm) : 0, realm,strlen(username), username,path ? strlen(path) : 0, path,port,protocolType,authenticationType,strlen(password), password,&itemRef);

身份验证防火墙

身份验证防火墙与身份验证服务器非常相似,处理必须检查每个失败的HTTP请求的代理身份验证和服务器身份验证。这以为着,你需要单独存储(本地和永久)代理服务器和源服务器。因此,失败的HTTP响应的过程如下:

  • 确定响应的状态码是否为407(代理怀疑)。如果是,检查当地代理仓库和永久性代理仓库查找一个匹配的身份验证对象和证书。如果这些都没有一个匹配的对象和证书,然后请求用户证书。应用身份验证对象到HTTP请求并重试。

  • 确定响应的状态码是否为401(服务器怀疑)。如果是,遵循与407响应相同的过程,但是用原始服务器存储。

使用代理服务器有些细微的差别。首先,钥匙链调用的参数来自于代理主机和端口,而非一个源服务器的URL。第二,当要求用户输入用户名和密码,确保清楚的提示是什么密码。

通过这些指令,你的应用应该可以使用身份验证防火墙。

官方原文地址:

https://developer.apple.com/library/ios/documentation/Networking/Conceptual/CFNetwork/CFHTTPAuthenticationTasks/CFHTTPAuthenticationTasks.html#//apple_ref/doc/uid/TP30001132-CH8-SW1



推荐阅读
  • 计算机存储系统的层次结构及其优势
    本文介绍了计算机存储系统的层次结构,包括高速缓存、主存储器和辅助存储器三个层次。通过分层存储数据可以提高程序的执行效率。计算机存储系统的层次结构将各种不同存储容量、存取速度和价格的存储器有机组合成整体,形成可寻址存储空间比主存储器空间大得多的存储整体。由于辅助存储器容量大、价格低,使得整体存储系统的平均价格降低。同时,高速缓存的存取速度可以和CPU的工作速度相匹配,进一步提高程序执行效率。 ... [详细]
  • 向QTextEdit拖放文件的方法及实现步骤
    本文介绍了在使用QTextEdit时如何实现拖放文件的功能,包括相关的方法和实现步骤。通过重写dragEnterEvent和dropEvent函数,并结合QMimeData和QUrl等类,可以轻松实现向QTextEdit拖放文件的功能。详细的代码实现和说明可以参考本文提供的示例代码。 ... [详细]
  • 本文分享了一个关于在C#中使用异步代码的问题,作者在控制台中运行时代码正常工作,但在Windows窗体中却无法正常工作。作者尝试搜索局域网上的主机,但在窗体中计数器没有减少。文章提供了相关的代码和解决思路。 ... [详细]
  • Java序列化对象传给PHP的方法及原理解析
    本文介绍了Java序列化对象传给PHP的方法及原理,包括Java对象传递的方式、序列化的方式、PHP中的序列化用法介绍、Java是否能反序列化PHP的数据、Java序列化的原理以及解决Java序列化中的问题。同时还解释了序列化的概念和作用,以及代码执行序列化所需要的权限。最后指出,序列化会将对象实例的所有字段都进行序列化,使得数据能够被表示为实例的序列化数据,但只有能够解释该格式的代码才能够确定数据的内容。 ... [详细]
  • XML介绍与使用的概述及标签规则
    本文介绍了XML的基本概念和用途,包括XML的可扩展性和标签的自定义特性。同时还详细解释了XML标签的规则,包括标签的尖括号和合法标识符的组成,标签必须成对出现的原则以及特殊标签的使用方法。通过本文的阅读,读者可以对XML的基本知识有一个全面的了解。 ... [详细]
  • 利用Visual Basic开发SAP接口程序初探的方法与原理
    本文介绍了利用Visual Basic开发SAP接口程序的方法与原理,以及SAP R/3系统的特点和二次开发平台ABAP的使用。通过程序接口自动读取SAP R/3的数据表或视图,在外部进行处理和利用水晶报表等工具生成符合中国人习惯的报表样式。具体介绍了RFC调用的原理和模型,并强调本文主要不讨论SAP R/3函数的开发,而是针对使用SAP的公司的非ABAP开发人员提供了初步的接口程序开发指导。 ... [详细]
  • 预备知识可参考我整理的博客Windows编程之线程:https:www.cnblogs.comZhuSenlinp16662075.htmlWindows编程之线程同步:https ... [详细]
  • 本文介绍了一个适用于PHP应用快速接入TRX和TRC20数字资产的开发包,该开发包支持使用自有Tron区块链节点的应用场景,也支持基于Tron官方公共API服务的轻量级部署场景。提供的功能包括生成地址、验证地址、查询余额、交易转账、查询最新区块和查询交易信息等。详细信息可参考tron-php的Github地址:https://github.com/Fenguoz/tron-php。 ... [详细]
  • 本文介绍了在MFC下利用C++和MFC的特性动态创建窗口的方法,包括继承现有的MFC类并加以改造、插入工具栏和状态栏对象的声明等。同时还提到了窗口销毁的处理方法。本文详细介绍了实现方法并给出了相关注意事项。 ... [详细]
  • Android实战——jsoup实现网络爬虫,糗事百科项目的起步
    本文介绍了Android实战中使用jsoup实现网络爬虫的方法,以糗事百科项目为例。对于初学者来说,数据源的缺乏是做项目的最大烦恼之一。本文讲述了如何使用网络爬虫获取数据,并以糗事百科作为练手项目。同时,提到了使用jsoup需要结合前端基础知识,以及如果学过JS的话可以更轻松地使用该框架。 ... [详细]
  • http:my.oschina.netleejun2005blog136820刚看到群里又有同学在说HTTP协议下的Get请求参数长度是有大小限制的,最大不能超过XX ... [详细]
  • 这篇文章主要介绍了Python拼接字符串的七种方式,包括使用%、format()、join()、f-string等方法。每种方法都有其特点和限制,通过本文的介绍可以帮助读者更好地理解和运用字符串拼接的技巧。 ... [详细]
  • 本文介绍了绕过WAF的XSS检测机制的方法,包括确定payload结构、测试和混淆。同时提出了一种构建XSS payload的方法,该payload与安全机制使用的正则表达式不匹配。通过清理用户输入、转义输出、使用文档对象模型(DOM)接收器和源、实施适当的跨域资源共享(CORS)策略和其他安全策略,可以有效阻止XSS漏洞。但是,WAF或自定义过滤器仍然被广泛使用来增加安全性。本文的方法可以绕过这种安全机制,构建与正则表达式不匹配的XSS payload。 ... [详细]
  • 修复安装win10失败并提示“磁盘布局不受UEFI固件支持”的方法
    本文介绍了修复安装win10失败并提示“磁盘布局不受UEFI固件支持”的方法。首先解释了UEFI的概念和作用,然后提供了两种解决方法。第一种方法是在bios界面中将Boot Mode设置为Legacy Support,Boot Priority设置为Legacy First,并关闭UEFI。第二种方法是使用U盘启动盘进入PE系统,运行磁盘分区工具DiskGenius,将硬盘的分区表设置为gpt格式,并留出288MB的内存。最后,通过运行界面输入命令cmd来完成设置。 ... [详细]
  • Vagrant虚拟化工具的安装和使用教程
    本文介绍了Vagrant虚拟化工具的安装和使用教程。首先介绍了安装virtualBox和Vagrant的步骤。然后详细说明了Vagrant的安装和使用方法,包括如何检查安装是否成功。最后介绍了下载虚拟机镜像的步骤,以及Vagrant镜像网站的相关信息。 ... [详细]
author-avatar
qyc_3830179
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有