【发布时间】:2015-09-15 22:23:54
【问题描述】:
我正在尝试扩展SocketRocket 库的功能。我想添加认证功能。
由于这个库使用 CFNetwork CFHTTPMessage* API 来实现 HTTP 功能(需要启动 Web 套接字连接),我正在尝试利用这个 API 来提供身份验证。
有一个完美匹配的功能:CFHTTPMessageAddAuthentication,但它不像我期望的那样工作(据我了解documentation)。
这是显示问题的代码示例:
- (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
user 和 password 值有效(“user”“passwd”)。
为什么CFHTTPMessageAddAuthentication 返回NO?不知道是什么问题。我也尝试使用凭据更新一个空请求,但没有运气。
我使用http://httpbin.org/ 只是为了测试(在这一步中网络套接字的功能无关紧要)。
请注意,使用过的代码不使用(并且永远不会)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> */
};
CFHTTPAuthenticationCreateFromResponse 返回无效对象,描述返回如下:
<CFHTTPAuthentication 0x108810450>{state = Failed; scheme = <undecided>, forProxy = false}
我在文档中找到了这些值的含义:domain=kCFStreamErrorDomainHTTP、error=kCFStreamErrorHTTPAuthenticationTypeUnsupported(感谢@JensAlfke,我在您发表评论之前就找到了它)。为什么不支持?文档声称支持摘要,有一个常量 kCFHTTPAuthenticationSchemeDigest 被 CFHTTPMessageAddAuthentication 接受和预期!
我已经挖掘了source code of
CFNetwork authentication 并试图找出问题所在。
我不得不犯一些错误,因为这个简单的品尝应用程序也失败了:
#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
在我自己的回答后编辑:
替代解决方案
其他可能的解决方案是手动解析 WWW-Authenticate 响应标头并对其进行处理并为新请求生成 Authorization 标头。
是否有一些我可以在商业应用程序中使用的简单库或示例代码(仅此)?我可以自己做,但这将花费宝贵的时间。赏金仍然可用:)。
【问题讨论】:
-
请注意,这是低级 API
CFHTTPMessage,它在CFStream上运行,您指的是更高级别的 APINSURLConnection或NSURLSession。出于某种奇怪的原因,CFHTTPMessageAddAuthentication拒绝将身份验证数据添加到我的请求中,并且没有任何信息说明原因。 -
我明白你对 CFStream 的意思。您是否尝试过使用
- (id)initWithURLRequest:(NSURLRequest *)request;传入NSURLRequest(实际上是NSMutableURLRequest对象)中的Auth 对象?只是不确定当它通过_urlRequest.allHTTPHeaderFields时,它会使用CFHTTPMessageSetHeaderFieldValue(request, (__bridge CFStringRef)key, (__bridge CFStringRef)obj);正确添加Auth 对象 -
@sbarow:显然你不明白这个问题。
NSURLRequest此处不可用,因为 HTTP 仅用作握手以启动 Web 套接字连接,因此较低级别的 API 用于 HTTP。 -
您使用的是 Socket Rocket 对吗?我不知道你的实现的进出,但是如果你在头文件(
SRWebSocket.h)中查看64行,你会看到- (id)initWithURLRequest:(NSURLRequest *)request;所以NSURLRequest实际上是可用的。就像我说我不知道你的实现,所以这就是我能给出的所有建议。祝你好运。 -
见the source code of initWithURLRequest。
NSURLRequest仅用作标题和 URL 的临时存储,没有其他用途。对于 HTTP 协议,仅使用 CFNetwork API。
标签: ios objective-c macos digest-authentication cfnetwork