我正在尝试扩展功能SocketRocket https://github.com/square/SocketRocket图书馆。我想添加身份验证功能。
由于该库正在使用CFNetwork CFHTTPMessage* API https://developer.apple.com/library/prerelease/ios/documentation/CoreFoundation/Reference/CFMessageRef/index.html对于 HTTP 功能(需要启动 Web 套接字连接),我正在尝试利用此 API 来提供身份验证。
有一个完美匹配的函数:CFHTTPMessageAddAuthentication
,但它没有按照我的预期工作(据我了解文档 https://developer.apple.com/library/prerelease/ios/documentation/CoreFoundation/Reference/CFMessageRef/index.html#//apple_ref/c/func/CFHTTPMessageAddAuthentication).
这是显示问题的代码示例:
- (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="[email protected] /cdn-cgi/l/email-protection", qop=auth
Content-Length: 0
Connection: keep-alive
user
and password
值有效(“用户”“密码”)。
Why CFHTTPMessageAddAuthentication
回报NO
?不知道问题出在哪里。我也尝试使用空请求更新凭据,但没有运气。
我用过http://httpbin.org/
仅用于测试(网络套接字的功能与此步骤无关)。
请注意,使用过的代码不会使用(也永远不会)NSURLRequst
or NSURLSession
or NSURLConnection
/
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
返回无效对象,其描述返回此:
<CFHTTPAuthentication 0x108810450>{state = Failed; scheme = <undecided>, forProxy = false}
我在文档中找到了这些值的含义:domain=kCFStreamErrorDomainHTTP
, error=kCFStreamErrorHTTPAuthenticationTypeUnsupported
(感谢@JensAlfke,我在您发表评论之前就找到了它)。为什么不支持?文档声称支持摘要,有一个常量kCFHTTPAuthenticationSchemeDigest
这是被接受和期望的CFHTTPMessageAddAuthentication
!
I've dig up
source code of CFNetwork authentication http://www.opensource.apple.com/source/CFNetwork/CFNetwork-128/HTTP/CFHTTPAuthentication.c and trying figure out what is the problem.
我必须犯一些错误,因为这个简单的tast应用程序也失败了:
#import <Foundation/Foundation.h>
#import <CFNetwork/CFNetwork.h>
static NSString * const kHTTPAuthHeaderName = @"WWW-Authenticate";
static NSString * const kHTTPDigestChallengeExample1 = @"Digest realm=\"[email protected] /cdn-cgi/l/email-protection\", "
"qop=\"auth,auth-int\", "
"nonce=\"dcd98b7102dd2f0e8b11d0f600bfb0c093\", "
"opaque=\"5ccc069c403ebaf9f0171e9517f40e41\"";
static NSString * const kHTTPDigestChallengeExample2 = @"Digest nonce=\"b6921981b6437a4f138ba7d631bcda37\", "
"opaque=\"3de7d2bd5708ac88904acbacbbebc4a2\", "
"realm=\"[email protected] /cdn-cgi/l/email-protection\", "
"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="[email protected] /cdn-cgi/l/email-protection", qop="auth,auth-int", nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093", opaque="5ccc069c403ebaf9f0171e9517f40e41"
HTTP/1.1 401 Unauthorized
Www-Authenticate: Digest realm="[email protected] /cdn-cgi/l/email-protection", 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="[email protected] /cdn-cgi/l/email-protection", qop=auth
HTTP/1.1 401 Unauthorized
Www-Authenticate: Digest nonce="b6921981b6437a4f138ba7d631bcda37", opaque="3de7d2bd5708ac88904acbacbbebc4a2", realm="[email protected] /cdn-cgi/l/email-protection", 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
响应头并对其进行处理并生成Authorization
新请求的标头。
是否有一些简单的库或示例代码可以在商业应用程序中使用来执行此操作(仅此)?我可以自己做这件事,但这需要花费宝贵的时间。赏金仍然可用:)。