Mar*_*k R 8 macos objective-c cfnetwork digest-authentication ios
我正在尝试扩展SocketRocket库的功能.我想添加身份验证功能.
由于此库使用CFNetwork
CFHTTPMessage*
API进行HTTP功能(需要启动Web套接字连接),我正在尝试使用此API来提供身份验证.
有完美匹配的功能: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;
}
Run Code Online (Sandbox Code Playgroud)
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/
Run Code Online (Sandbox Code Playgroud)
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
Run Code Online (Sandbox Code Playgroud)
user
和password
值有效("user""passwd").
为何CFHTTPMessageAddAuthentication
回归NO
?不知道问题是什么.我也尝试使用凭据更新空请求,但没有运气.
我http://httpbin.org/
只是用于测试(在这一步中,Web套接字的功能无关紧要).
请不要使用的代码不使用(永远不会)NSURLRequst
或NSURLSession
或NSURLConnection
/
CFHTTPAuthenticationCreateFromResponse
并CFHTTPMessageApplyCredentials
得到相同的结果.至少CFHTTPMessageApplyCredentials
以形式返回一些错误信息CFStreamError
.问题是,这个错误信息是无用的:error.domain = 4
,error.error = -1000
其中,这些值不记录任何地方.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> */
};
Run Code Online (Sandbox Code Playgroud)
CFHTTPAuthenticationCreateFromResponse
返回无效对象,哪个描述返回:
<CFHTTPAuthentication 0x108810450>{state = Failed; scheme = <undecided>, forProxy = false}
Run Code Online (Sandbox Code Playgroud)
我在文档中发现了这些值意味着什么:domain=kCFStreamErrorDomainHTTP
,error=kCFStreamErrorHTTPAuthenticationTypeUnsupported
(感谢@JensAlfke我在你的评论之前找到了它).为什么不支持?文档声称支持摘要有一个常量kCFHTTPAuthenticationSchemeDigest
被接受和期望CFHTTPMessageAddAuthentication
!
CFNetwork
并试图找出问题所在.
我必须犯一些错误,因为这个简单的tast应用程序也会失败:
#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;
}
Run Code Online (Sandbox Code Playgroud)
日志显示:
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
Run Code Online (Sandbox Code Playgroud)
其他可能的解决方案是手动解析WWW-Authenticate
响应头并进行响应,并Authorization
为新请求生成头.
是否有一些我可以在商业应用程序中使用的简单库或示例代码(只有这个)?我能做到这一点,但这需要宝贵的时间.赏金仍然可用:).
回答自己的问题:(
CFNetwork
API很糟糕问题是响应中CFHTTPMessageRef
有隐藏属性URL
。您可以阅读它:CFHTTPMessageCopyRequestURL
不设置它,并且需要从 正确创建身份验证对象CFHTTPMessageRef
。如果URL
属性为空,身份验证将失败。
那么为什么在某些情况下包含身份验证质询的响应URL
在其他情况下不包含呢?该工作响应来自作为该流的属性CFReadStreamRef
创建的。这是蹩脚的例子。因此,既然不使用,这是一个无法简单克服的大问题。CFReadStreamCreateForHTTPRequest
SocketRocket
CFReadStreamCreateForHTTPRequest
令人遗憾的是,如果无法在响应中找到CFHTTPMessageAddAuthentication
它,则可以从它修改的请求中获取它。URL
对于这个问题,有一个完美的解决方法!但它涉及到私有 API 的使用(所以很可能不会通过 Apple 审核)。这是带有解决方法的完整示例代码(与有问题的相同,但应用了此解决方法),解决方法本身只有两行:公开私有 API 并使用它。
#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;
}
Run Code Online (Sandbox Code Playgroud)
日志中的结果如下所示:
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
Run Code Online (Sandbox Code Playgroud)
所以解决方法有效。
我将继续寻找仅使用公共 API 的其他解决方法。至少现在我知道问题是什么了。
归档时间: |
|
查看次数: |
917 次 |
最近记录: |