与身份验证HTTP服务器通信(Communicating with Authenticating HTTP Servers)

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

一般来说,如果一个HTTP服务器返回一个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-2中的代码。如果新身份验证对象有效,那么你已经完成可以继续图4-1的第二步。如果身份验证对象无效,然后扔掉身份验证对象和证书,检查证书。关于证书的更多信息,阅读安全证书(Security Credentials)。

清单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 necessary
        if (!CFHTTPAuthenticationRequiresAccountDomain(authentication)) {
            realm = CFHTTPAuthenticationCopyRealm(authentication);
        }
        // ...prompt user for user name (user), password (pass)
        // and if necessary domain (domain) to give to the server...

        // Guarantee values
        if (!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 load
        loadRequest();
    }
}

在内存中存储证书

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

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

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

CFMutableArrayRef authArray;

代替:

CFHTTPAuthenticationRef authentication;

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

CFMutableDictionaryRef credentialsDict;

代替:

CFMutableDictionaryRef credentials;

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

CFDictionaryRemoveValue(credentialsDict, authentication);

代替:

CFRelease(credentials);

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

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

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

credentials = CFDictionaryGetValue(credentialsDict, authentication);

警告:在接收到服务器验证前,不要应用证书到HTTP请求。在你上次认证后,服务器可能改变,你可能会有一个安全风险。然后应用你的匹配的身份验证对象和证书到你原始的HTTP请求并重新发送。

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

在永久性仓库中存储证书

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

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

CFMutableDictionaryRef findCredentialsForAuthentication(
        CFHTTPAuthenticationRef auth);

void saveCredentialsForRequest(void);

如果内存中没有证书的缓存,然后搜索钥匙链。使用SecKeychainFindInternetPassword函数搜索钥匙链。该函数需要大量的参数。参数和一段简短的描述HTTP身份验证证书如何使用它们,如下:findCredentialsForAuthentication 函数首先检查内存中的证书字典本地缓存是否有证书。如何实现见清单4-6。

keychainOrArray

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

serverNameLength

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

serverName

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

securityDomainLength

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

securityDomain

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

accountNameLength

accountName的长度。由于accountName是NULL,值为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);

用户名和密码可以用来创建一组新证书。清单4-8展示了这个过程。假设SecKeychainFindInternetPassword 成功返回,创建一个包含单独钥匙链属性(SecKeychainAttribute)的钥匙链属性列表(SecKeychainAttributeList)。钥匙链实现列表将包含用户名和密码。为了加载钥匙链属性列表,调用SecKeychainItemCopyContent 函数并将SecKeychainFindInternetPassword返回的钥匙链记录引用对象(itemRef)传递给它。该函数将用账号的用户名和密码void **填充到钥匙链属性中。

清单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 password
        CFStringRef 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 & password
        credentials =
            CFDictionaryCreateMutable(NULL, 0,
                                      &kCFTypeDictionaryKeyCallBacks,
                                      &kCFTypeDictionaryValueCallBacks);
        CFDictionarySetValue(credentials, kCFHTTPAuthenticationUsername,
                             username);
        CFDictionarySetValue(credentials, kCFHTTPAuthenticationPassword,
                             password);

        CFRelease(username);
        CFRelease(password);
    }
    CFRelease(itemRef);
}

如果记录存在,修改它来改变密码。设置钥匙链属性的数据字段包含用户名,主要你可以修改正确的属性。然后调用SecKeychainItemModifyContent 函数并传递钥匙链记录引用对象(itemRef),钥匙链属性列表和新密码。通过修改钥匙链记录而非重写,钥匙链记录会正确的更新其他相关数据也将保留。记录如清单4-9所示。如果你可以先存储证书到钥匙链中,从钥匙链中检索证书才有用。首先,查看证书是否已经存储在钥匙链中。调用SecKeychainFindInternetPassword,传递用户名到accountName ,传递accountName 的长度到accountNameLength。

清单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响应的过程如下:

  1. 确定响应的状态码是否为407(代理怀疑)。如果是,检查当地代理仓库和永久性代理仓库查找一个匹配的身份验证对象和证书。如果这些都没有一个匹配的对象和证书,然后请求用户证书。应用身份验证对象到HTTP请求并重试。
  2. 确定响应的状态码是否为401(服务器怀疑)。如果是,遵循与407响应相同的过程,但是用原始服务器存储。

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

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

官方原文地址:

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

时间: 2024-10-24 17:00:16

与身份验证HTTP服务器通信(Communicating with Authenticating HTTP Servers)的相关文章

iOS Communicating with Authenticating HTTP Servers 与HTTP服务器通信认证 官方文档翻译(六)

原文地址:developer 召集翻译人手:请加入486111671  ios 官方文档翻译                                                            与HTTP服务器通信认证 本章描述了如何与HTTP服务器身份验证利用CFHTTPAuthentication API.它解释了如何找到匹配验证对象和凭证,将它们应用到一个HTTP请求,并将它们存储供以后使用. 一般来说,如果一个HTTP服务器将返回一个401或407响应后你的HTTP请求

sqlserver 2012 IDE中 Windows身份验证连接服务器报错 ,Login failed for user &#39;xxx\Administrator&#39;. 原因: 找不到与提供的名称匹配的登录名。

问题描述: 本地装了两个实例,一个是SQLEXPRESS,可以正常操作.但是另一个开发常用的实例MSSQLSERVER却连Windows身份验证都报错,报的错误也是很奇葩,怎么会找不到Administrator那,我登录计算机进来就用的这个账号. 详细错误如下: 无法连接到 xxx.===================================用户 'xxx\Administrator' 登录失败. (.Net SqlClient Data Provider)--------------

与HTTP服务器通信(Communicating with HTTP Servers)

本文解释了如何创建.发送和接收HTTP请求和响应. 创建一个CFHTTP请求 HTTP请求是一个消息,这个消息由远程服务器执行的方法,操作的对象(URL),消息头和消息体.方法通常是下面之一:GET, HEAD, PUT, POST, DELETE, TRACE, CONNECT 或OPTIONS.用CFHTTP创建一个HTTP请求分为四个步骤: 使用CFHTTPMessageCreateRequest 函数生成CFHTTP消息对象 使用CFHTTPMessageSetBody函数设置消息体 使

ios Communicating with HTTP Servers 与HTTP服务器通信 官方文档翻译(五)

原文地址:developer 时间过的很快,一眨眼周末就过了.人的一生到底怎么样才能过的有意义呢?我一直在找答案.做自己喜欢的事情,爱自己喜欢的人,或是成就一番伟业.都是不错的选择,找到自己想要做的事情,不要放弃寻找.最终生命带走的只能是珍贵的回忆,晚上快乐. 与HTTP服务器通信 本章解释了如何创建.发送和接收HTTP请求和响应. 创建一个CFHTTP请求 组成一个HTTP请求消息的远程服务器执行的方法,对象操作(URL),消息头和消息体.方法通常是下列之一:GET, HEAD, PUT, P

RFC7619 -- IKEv2中的NULL身份验证方法

摘要 本文为IKEv2指定NULL身份验证方法和ID_NULL标识有效负载ID类型.这允许两个IKE对等体建立单侧认证或相互不认证的IKE会话. 适用于那些对等方不愿意或无法进行自身身份验证或识别的情形.这可确保IKEv2可用于机会安全(也称为机会加密),以抵御普遍监控攻击,而无需牺牲匿名性???. 1. 简介 虽然对等方使用的身份验证方法可能不同,但是没有方法可以让一方或双方保持未经身份验证和匿名. 本文档扩展了身份验证方法,以支持未经身份验证的匿名IKE会话. 在某些情况下,相互认证是不需要

14周翻译:SQL Server安全级别2的阶梯:身份验证 作者:Don Kiely,2014/06/18 该系列 本文是“Stairway系列:SQL Server安全性的阶梯”的一部分 链接:http://www.sqlservercentral.com/articles/Stairway+Series/109975/

正文: SQL Server提供了保护服务器和数据免受当今复杂攻击所需的一切.但是,在您有效使用这些安全功能之前,您需要了解您面临的威胁以及一些基本的安全概念.这第一阶梯级别提供了一个基础,以便您可以充分利用SQL Server中的安全功能,而不会浪费时间在功能上无所事事,以防止对数据的特定威胁. 验证是验证主体(需要访问SQL Server数据库的用户或进程)是谁或声称是什么的过程.主体需要唯一标识,以便SQL Server可以确定主体拥有哪些权限(如果有).正确的身份验证是提供对数据库对象安

sql server 无法用sql server身份验证

1)首先,用windows身份验证进入服务器. 2)其次找到安全性,点击进入后,找到登录名为sa,然后右击属性. 3)在属性中找到常规,然后检查下自己的账号和密码,并且在状态中将登陆状态改成启用,否则禁用状态不可运行. 4)接着,点击服务器右击属性,更改安全性中的服务器身份验证,然后点击确定,会弹出一个重新启用.然后点击服务器右击重新启用.再断开连接重新用sqlserver身份登录.记住!修改确定后一定要将服务器重新启动,否则还是会有相同的问题存在,直到他显示"是否确定要重新启动sqlserve

xmpp 与服务器连接并身份验证成功

*  XMPP的特点,所有的请求都是通过代理的方式实现的 * *  因为xmpp是经由网络服务器进行数据通讯的,因此所有的,因此所有的请求都是提交给服务器处理 * *  服务器处理完毕止呕,以代理的方式告诉客户端处理结果 * *  官方推荐AppDelegate处理所有来自XMPP服务器的代理响应 AppDelegate.h // // AppDelegate.h // xmpp练习 // // Created by tqh on 15/4/12. // Copyright (c) 2015年

解决WCF 调用方未由服务器进行身份验证或消息包含无效或过期的安全上下文令牌

错误描述: 1. WCF:调用方未由服务器进行身份验证 2. 无法处理消息.这很可能是因为操作“http://tempuri.org/ISCCLSvc/GetCarriersByWareHouse”不正确,或因为消息包含无效或过期的安全上下文令牌,或因为绑定之间出现不匹配.如果由于未处于活动状态导致服务中止了该通道,则安全上下文令牌无效.若要防止服务永久中止闲置会话,请增加服务终结点绑定上的接收超时. 3.或并发测试时,高并发出现问题2,实际案例:公司测试部门结果是并发到50就会出现问题2错误,