CFHTTPMessageAddAuthentication无法向请求添加身份验证数据 [英] CFHTTPMessageAddAuthentication fails to add authentication data to request

查看:165
本文介绍了CFHTTPMessageAddAuthentication无法向请求添加身份验证数据的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试扩展 SocketRocket 库的功能。我想添加身份验证功能。

I'm trying to extend functionality of SocketRocket library. I want to add authentication feature.

由于此库正在使用 CFNetwork CFHTTPMessage * API 用于HTTP功能(启动Web套接字连接所需)我正在尝试利用此API提供身份验证。

有完全匹配的功能: CFHTTPMessageAddAuthentication ,但它不能像我期望的那样工作(据我所知文档)。

Since this library is using CFNetwork CFHTTPMessage* API for HTTP functionality (needed to start web socket connection) I'm trying to utilize this API to provide authentication.
There is perfectly matching function for that: CFHTTPMessageAddAuthentication, but it doesn't work as I'm expecting (as I understand documentation).

以下是显示问题的代码示例:

Here is sample of code showing the problem:

- (CFHTTPMessageRef)createAuthenticationHandShakeRequest: (CFHTTPMessageRef)chalengeMessage {
    CFHTTPMessageRef request = [self createHandshakeRequest];
    BOOL result = CFHTTPMessageAddAuthentication(request,
                                                 chalengeMessage,
                                                 (__bridge CFStringRef)self.credentials.user,
                                                 (__bridge CFStringRef)self.credentials.password,
                                                 kCFHTTPAuthenticationSchemeDigest, /* I've also tried NULL for use strongest supplied authentication */
                                                 NO);
    if (!result) {
        NSString *chalengeDescription = [[NSString alloc] initWithData: CFBridgingRelease(CFHTTPMessageCopySerializedMessage(chalengeMessage))
                                                              encoding: NSUTF8StringEncoding];
        NSString  *requestDescription = [[NSString alloc] initWithData: CFBridgingRelease(CFHTTPMessageCopySerializedMessage(request))
                                                              encoding: NSUTF8StringEncoding];
        SRFastLog(@"Failed to add authentication data `%@` to a request:\n%@After a chalenge:\n%@",
                  self.credentials, requestDescription, chalengeDescription);
    }
    return request;
}

requestDescription 内容为:

GET /digest-auth/auth/user/passwd HTTP/1.1
Host: httpbin.org
Sec-WebSocket-Version: 13
Upgrade: websocket
Sec-WebSocket-Key: 3P5YiQDt+g/wgxHe71Af5Q==
Connection: Upgrade
Origin: http://httpbin.org/

chalengeDescription 包含:

HTTP/1.1 401 UNAUTHORIZED
Server: nginx
Content-Type: text/html; charset=utf-8
Set-Cookie: fake=fake_value
Access-Control-Allow-Origin: http://httpbin.org/
Access-Control-Allow-Credentials: true
Date: Mon, 29 Jun 2015 12:21:33 GMT
Proxy-Support: Session-Based-Authentication
Www-Authenticate: Digest nonce="0c7479b412e665b8685bea67580cf391", opaque="4ac236a2cec0fc3b07ef4d628a4aa679", realm="me@kennethreitz.com", qop=auth
Content-Length: 0
Connection: keep-alive

用户密码值有效(userpasswd) 。

user and password values are valid ("user" "passwd").

为什么 CFHTTPMessageAddAuthentication 返回?不知道问题是什么。我也尝试使用凭据更新空请求但没有运气。

Why CFHTTPMessageAddAuthentication returns NO? There is no clue what is the problem. I've also try updated with credentials an empty request but without luck.

我使用过 http://httpbin.org/ 仅用于测试(在此步骤中,Web套接字的功能无关紧要)。

I've used http://httpbin.org/ just for testing (functionality of web socket is irrelevant at this step).

请注意,使用过的代码不会使用(并且永远不会) NSURLRequst NSURLSession NSURLConnection /

Please not that used code doesn't use (and never will) NSURLRequst or NSURLSession or NSURLConnection/



我尝试使用不同的功能: CFHTTPAuthenticationCreateFromResponse CFHTTPMessageApplyCredentials 结果相同。
至少 CFHTTPMessageApplyCredentials CFStreamError 的形式返回一些错误信息。问题是这个错误信息没用: error.domain = 4 error.error = -1000 这些值的位置没有记录在任何地方。

唯一记录的值如下所示:


I've tried to use different functions: CFHTTPAuthenticationCreateFromResponse and CFHTTPMessageApplyCredentials with same result. At least CFHTTPMessageApplyCredentials returns some error information in form of CFStreamError. Problem is that this error information is useless: error.domain = 4, error.error = -1000 where those values are not documented anywhere.
The only documented values looks like this:

typedef CF_ENUM(CFIndex, CFStreamErrorDomain) {
    kCFStreamErrorDomainCustom = -1L,      /* custom to the kind of stream in question */
    kCFStreamErrorDomainPOSIX = 1,        /* POSIX errno; interpret using <sys/errno.h> */
    kCFStreamErrorDomainMacOSStatus      /* OSStatus type from Carbon APIs; interpret using <MacTypes.h> */
};

CFHTTPAuthenticationCreateFromResponse 返回无效对象,哪些描述返回这个:

CFHTTPAuthenticationCreateFromResponse returns invalid object, which description returns this:

<CFHTTPAuthentication 0x108810450>{state = Failed; scheme = <undecided>, forProxy = false}

我在文档中发现这些值意味着什么: domain = kCFStreamErrorDomainHTTP error = kCFStreamErrorHTTPAuthenticationTypeUnsupported (感谢@JensAlfke我在你的评论之前找到了它)。为什么它不受支持?文档声称支持摘要有一个常量 kCFHTTPAuthenticationSchemeDigest 这是 CFHTTPMessageAddAuthentication 接受和预期的!

I've found in documentation what those values means: domain=kCFStreamErrorDomainHTTP, error=kCFStreamErrorHTTPAuthenticationTypeUnsupported (thanks @JensAlfke I've found it before your comment). Why it is unsupported? Documentation claims that digest is supported there is a constant kCFHTTPAuthenticationSchemeDigest which is accepted and expected by CFHTTPMessageAddAuthentication!



我挖掘了源代码 CFNetwork 身份验证并尝试找出问题所在。


I've dig up source code of CFNetwork authentication and trying figure out what is the problem.


我必须犯一些错误,因为这个简单的tast应用程序也会失败:

I have to do some mistake since this simple tast application also fails:

#import <Foundation/Foundation.h>
#import <CFNetwork/CFNetwork.h>

static NSString * const kHTTPAuthHeaderName = @"WWW-Authenticate";

static NSString * const kHTTPDigestChallengeExample1 = @"Digest realm=\"testrealm@host.com\", "
    "qop=\"auth,auth-int\", "
    "nonce=\"dcd98b7102dd2f0e8b11d0f600bfb0c093\", "
    "opaque=\"5ccc069c403ebaf9f0171e9517f40e41\"";

static NSString * const kHTTPDigestChallengeExample2 = @"Digest nonce=\"b6921981b6437a4f138ba7d631bcda37\", "
    "opaque=\"3de7d2bd5708ac88904acbacbbebc4a2\", "
    "realm=\"me@kennethreitz.com\", "
    "qop=auth";

static NSString * const kHTTPBasicChallengeExample1 = @"Basic realm=\"Fake Realm\"";

#define RETURN_STRING_IF_CONSTANT(a, x) if ((a) == (x)) return @ #x

NSString *NSStringFromCFErrorDomain(CFIndex domain) {
    RETURN_STRING_IF_CONSTANT(domain, kCFStreamErrorDomainHTTP);
    RETURN_STRING_IF_CONSTANT(domain, kCFStreamErrorDomainFTP);
    RETURN_STRING_IF_CONSTANT(domain, kCFStreamErrorDomainSSL);
    RETURN_STRING_IF_CONSTANT(domain, kCFStreamErrorDomainSystemConfiguration);
    RETURN_STRING_IF_CONSTANT(domain, kCFStreamErrorDomainSOCKS);
    RETURN_STRING_IF_CONSTANT(domain, kCFStreamErrorDomainPOSIX);
    RETURN_STRING_IF_CONSTANT(domain, kCFStreamErrorDomainMacOSStatus);

    return [NSString stringWithFormat: @"UnknownDomain=%ld", domain];
}

NSString *NSStringFromCFErrorError(SInt32 error) {
    RETURN_STRING_IF_CONSTANT(error, kCFStreamErrorHTTPAuthenticationTypeUnsupported);
    RETURN_STRING_IF_CONSTANT(error, kCFStreamErrorHTTPAuthenticationBadUserName);
    RETURN_STRING_IF_CONSTANT(error, kCFStreamErrorHTTPAuthenticationBadPassword);

    return [NSString stringWithFormat: @"UnknownError=%d", (int)error];
}

NSString *NSStringFromCFHTTPMessage(CFHTTPMessageRef message) {
    return [[NSString alloc] initWithData: CFBridgingRelease(CFHTTPMessageCopySerializedMessage(message))
                                 encoding: NSUTF8StringEncoding];
}

void testAuthenticationHeader(NSString *authenticatiohHeader) {
    CFHTTPMessageRef response = CFHTTPMessageCreateResponse(kCFAllocatorDefault,
                                                            401,
                                                            NULL,
                                                            kCFHTTPVersion1_1);
    CFAutorelease(response);

    CFHTTPMessageSetHeaderFieldValue(response,
                                     (__bridge CFStringRef)kHTTPAuthHeaderName,
                                     (__bridge CFStringRef)authenticatiohHeader);


    CFHTTPAuthenticationRef authData = CFHTTPAuthenticationCreateFromResponse(kCFAllocatorDefault, response);
    CFAutorelease(authData);

    CFStreamError error;
    BOOL validAuthData = CFHTTPAuthenticationIsValid(authData, &error);

    NSLog(@"testing header value: %@\n%@authData are %@   error.domain=%@  error.error=%@\n\n",
          authenticatiohHeader, NSStringFromCFHTTPMessage(response),
          validAuthData?@"Valid":@"INVALID",
          NSStringFromCFErrorDomain(error.domain), NSStringFromCFErrorError(error.error));
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        testAuthenticationHeader(kHTTPDigestChallengeExample1);
        testAuthenticationHeader(kHTTPDigestChallengeExample2);
        testAuthenticationHeader(kHTTPBasicChallengeExample1);
    }
    return 0;
}

日志显示:

2015-07-01 16:33:57.659 cfauthtest[24742:600143] testing header value: Digest realm="testrealm@host.com", qop="auth,auth-int", nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093", opaque="5ccc069c403ebaf9f0171e9517f40e41"
HTTP/1.1 401 Unauthorized
Www-Authenticate: Digest realm="testrealm@host.com", qop="auth,auth-int", nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093", opaque="5ccc069c403ebaf9f0171e9517f40e41"

authData are INVALID   error.domain=kCFStreamErrorDomainHTTP  error.error=kCFStreamErrorHTTPAuthenticationTypeUnsupported

2015-07-01 16:33:57.660 cfauthtest[24742:600143] testing header value: Digest nonce="b6921981b6437a4f138ba7d631bcda37", opaque="3de7d2bd5708ac88904acbacbbebc4a2", realm="me@kennethreitz.com", qop=auth
HTTP/1.1 401 Unauthorized
Www-Authenticate: Digest nonce="b6921981b6437a4f138ba7d631bcda37", opaque="3de7d2bd5708ac88904acbacbbebc4a2", realm="me@kennethreitz.com", qop=auth

authData are INVALID   error.domain=kCFStreamErrorDomainHTTP  error.error=kCFStreamErrorHTTPAuthenticationTypeUnsupported

2015-07-01 16:33:57.660 cfauthtest[24742:600143] testing header value: Basic realm="Fake Realm"
HTTP/1.1 401 Unauthorized
Www-Authenticate: Basic realm="Fake Realm"

authData are INVALID   error.domain=kCFStreamErrorDomainHTTP  error.error=kCFStreamErrorHTTPAuthenticationTypeUnsupported



在我自己的回答后编辑:


edit after my own answer:

其他可能的解决方案是手动解析 WWW-Authenticate 响应标题并进行处理,并为新请求生成授权标题。

Other possible solution is to manually parse WWW-Authenticate response header and precess it and generate Authorization header for new request.

是否有一些我可以在商业应用程序中使用的简单库或示例代码(只有这个)?我能做到这一点,但这需要宝贵的时间。 Bounty仍然可用:)。

Is there some simple library or sample code I could use in commercial application which will do this (only this)? I could do this my self but this will take a precious time. Bounty is still available :).

推荐答案

回答自己的问题:(

问题是 CFHTTPMessageRef中的响应隐藏属性 URL
你可以读它: CFHTTPMessageCopyRequestURL 没有设置它,它是需要从 CFHTTPMessageRef 正确创建身份验证对象。如果 URL 属性为空,则身份验证将失败。

Problem is that response in CFHTTPMessageRef have hidden property URL. You can read it: CFHTTPMessageCopyRequestURL not set it and it is needed to properly create authentication object from CFHTTPMessageRef. If URL property is empty authentication will fail.

那么为什么有些情况下,身份验证质询的响应包含 URL 在其他情况下不是?
此工作响应来自 CFReadStreamRef CFReadStreamCreateForHTTPRequest 创建,作为此流的属性。这是一个糟糕的例子。所以,因为 SocketRocket 不使用 CFReadStreamCreateForHTTPRequest 这是一个无法简单克服的大问题。

So how come that is some cases response with authentication challenge contains URL in other cases not? This working response comes from CFReadStreamRef created by CFReadStreamCreateForHTTPRequest as property of this stream. Here is crappy example. So since SocketRocket doesn't use CFReadStreamCreateForHTTPRequest this is a big problem which can't be simply overcome.

令人遗憾的是, CFHTTPMessageAddAuthentication 可以获取此 URL 如果在响应中找不到,则会根据请求进行修改。

What is sad that CFHTTPMessageAddAuthentication could fetch this URL from request it modifies if it can't be found in response.

在这个问题上有完美的解决方法!但它涉及使用私有API(所以很可能它不会通过Apple审查)。以下是完整的示例代码及解决方法(与问题相同但应用此解决方法),解决方法只需两行:公开私有API并使用它。

There is perfectly working workaround on this issue! But it involves use of private API (so most probably it will not pass Apple review). Here is full sample code with workaround (same as in question but applying this workaround), the workaround it self it just two lines: exposing private API and using it.

#import <Foundation/Foundation.h>
#import <CFNetwork/CFNetwork.h>

static NSString * const kHTTPAuthHeaderName = @"WWW-Authenticate";

static NSString * const kHTTPDigestChallengeExample1 = @"Digest realm=\"testrealm@host.com\", "
    "qop=\"auth,auth-int\", "
    "nonce=\"dcd98b7102dd2f0e8b11d0f600bfb0c093\", "
    "opaque=\"5ccc069c403ebaf9f0171e9517f40e41\"";

static NSString * const kHTTPDigestChallengeExample2 = @"Digest nonce=\"b6921981b6437a4f138ba7d631bcda37\", "
    "opaque=\"3de7d2bd5708ac88904acbacbbebc4a2\", "
    "realm=\"me@kennethreitz.com\", "
    "qop=auth";

static NSString * const kHTTPBasicChallengeExample1 = @"Basic realm=\"Fake Realm\"";

#define RETURN_STRING_IF_CONSTANT(a, x) if ((a) == (x)) return @ #x

NSString *NSStringFromCFErrorDomain(CFIndex domain) {
    RETURN_STRING_IF_CONSTANT(domain, kCFStreamErrorDomainHTTP);
    RETURN_STRING_IF_CONSTANT(domain, kCFStreamErrorDomainFTP);
    RETURN_STRING_IF_CONSTANT(domain, kCFStreamErrorDomainSSL);
    RETURN_STRING_IF_CONSTANT(domain, kCFStreamErrorDomainSystemConfiguration);
    RETURN_STRING_IF_CONSTANT(domain, kCFStreamErrorDomainSOCKS);
    RETURN_STRING_IF_CONSTANT(domain, kCFStreamErrorDomainPOSIX);
    RETURN_STRING_IF_CONSTANT(domain, kCFStreamErrorDomainMacOSStatus);

    return [NSString stringWithFormat: @"UnknownDomain=%ld", domain];
}

NSString *NSStringFromCFErrorError(SInt32 error) {
    RETURN_STRING_IF_CONSTANT(error, kCFStreamErrorHTTPAuthenticationTypeUnsupported);
    RETURN_STRING_IF_CONSTANT(error, kCFStreamErrorHTTPAuthenticationBadUserName);
    RETURN_STRING_IF_CONSTANT(error, kCFStreamErrorHTTPAuthenticationBadPassword);

    return [NSString stringWithFormat: @"UnknownError=%d", (int)error];
}

NSString *NSStringFromCFHTTPMessage(CFHTTPMessageRef message) {
    return [[NSString alloc] initWithData: CFBridgingRelease(CFHTTPMessageCopySerializedMessage(message))
                                 encoding: NSUTF8StringEncoding];
}

// exposing private API for workaround
extern void _CFHTTPMessageSetResponseURL(CFHTTPMessageRef, CFURLRef);

void testAuthenticationHeader(NSString *authenticatiohHeader) {
    CFHTTPMessageRef response = CFHTTPMessageCreateResponse(kCFAllocatorDefault,
                                                            401,
                                                            NULL,
                                                            kCFHTTPVersion1_1);
    CFAutorelease(response);

    // workaround: use of private API
    _CFHTTPMessageSetResponseURL(response, (__bridge CFURLRef)[NSURL URLWithString: @"http://some.test.url.com/"]);

    CFHTTPMessageSetHeaderFieldValue(response,
                                     (__bridge CFStringRef)kHTTPAuthHeaderName,
                                     (__bridge CFStringRef)authenticatiohHeader);


    CFHTTPAuthenticationRef authData = CFHTTPAuthenticationCreateFromResponse(kCFAllocatorDefault, response);
    CFAutorelease(authData);

    CFStreamError error;
    BOOL validAuthData = CFHTTPAuthenticationIsValid(authData, &error);

    NSLog(@"testing header value: %@\n%@authData are %@   error.domain=%@  error.error=%@\n\n",
          authenticatiohHeader, NSStringFromCFHTTPMessage(response),
          validAuthData?@"Valid":@"INVALID",
          NSStringFromCFErrorDomain(error.domain), NSStringFromCFErrorError(error.error));
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        testAuthenticationHeader(kHTTPDigestChallengeExample1);
        testAuthenticationHeader(kHTTPDigestChallengeExample2);
        testAuthenticationHeader(kHTTPBasicChallengeExample1);
    }
    return 0;
}

结果日志看起来像这样:

And result in logs looks like that:

2015-07-03 11:47:02.849 cfauthtest[42766:934054] testing header value: Digest realm="testrealm@host.com", qop="auth,auth-int", nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093", opaque="5ccc069c403ebaf9f0171e9517f40e41"
HTTP/1.1 401 Unauthorized
Www-Authenticate: Digest realm="testrealm@host.com", qop="auth,auth-int", nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093", opaque="5ccc069c403ebaf9f0171e9517f40e41"

authData are Valid   error.domain=UnknownDomain=0  error.error=UnknownError=0

2015-07-03 11:47:02.852 cfauthtest[42766:934054] testing header value: Digest nonce="b6921981b6437a4f138ba7d631bcda37", opaque="3de7d2bd5708ac88904acbacbbebc4a2", realm="me@kennethreitz.com", qop=auth
HTTP/1.1 401 Unauthorized
Www-Authenticate: Digest nonce="b6921981b6437a4f138ba7d631bcda37", opaque="3de7d2bd5708ac88904acbacbbebc4a2", realm="me@kennethreitz.com", qop=auth

authData are Valid   error.domain=UnknownDomain=0  error.error=UnknownError=0

2015-07-03 11:47:02.852 cfauthtest[42766:934054] testing header value: Basic realm="Fake Realm"
HTTP/1.1 401 Unauthorized
Www-Authenticate: Basic realm="Fake Realm"

authData are Valid   error.domain=UnknownDomain=0  error.error=UnknownError=0

因此解决方法有效。

我将继续寻找仅使用公共API的其他解决方法。至少现在我知道这是什么问题。

I will keep looking for other workaround which will use public API only. At least now I know what is the problem.

这篇关于CFHTTPMessageAddAuthentication无法向请求添加身份验证数据的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆