如何在Objective-C中基于NSURLSessionDataTask集成摘要认证
在Objective-C的NSURLSession中集成摘要认证
你的思路完全正确——先发起请求获取服务端返回的Www-Authenticate头信息(包含nonce等关键参数),再基于这些参数生成摘要认证头,发起后续的认证请求。下面是完整的实现方案:
1. 解析Www-Authenticate头信息
首先需要把服务端返回的Www-Authenticate字符串(格式类似Digest realm="xxx", nonce="yyy", qop="auth")解析成字典,方便提取所需参数:
- (NSDictionary *)parseWwwAuthenticateHeader:(NSString *)headerString { NSMutableDictionary *params = [NSMutableDictionary dictionary]; // 跳过开头的"Digest "前缀 NSString *cleanedHeader = [headerString stringByReplacingOccurrencesOfString:@"Digest " withString:@""]; NSArray *components = [cleanedHeader componentsSeparatedByString:@","]; for (NSString *component in components) { NSArray *keyValue = [component componentsSeparatedByString:@"="]; if (keyValue.count == 2) { NSString *key = [[keyValue[0] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]] lowercaseString]; NSString *value = [keyValue[1] stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"\" "]]; params[key] = value; } } return params; }
2. 计算摘要认证的响应值
根据RFC 2617的规则,需要计算HA1、HA2和最终的response值:
#import <CommonCrypto/CommonDigest.h> - (NSString *)md5HashFromString:(NSString *)input { const char *cStr = [input UTF8String]; unsigned char digest[CC_MD5_DIGEST_LENGTH]; CC_MD5(cStr, (CC_LONG)strlen(cStr), digest); NSMutableString *result = [NSMutableString stringWithCapacity:CC_MD5_DIGEST_LENGTH * 2]; for(int i = 0; i < CC_MD5_DIGEST_LENGTH; i++) { [result appendFormat:@"%02x", digest[i]]; } return result; } - (NSString *)generateDigestResponseWithUsername:(NSString *)username password:(NSString *)password realm:(NSString *)realm nonce:(NSString *)nonce uri:(NSString *)uri method:(NSString *)method qop:(NSString *)qop { // 计算HA1: MD5(username:realm:password) NSString *ha1Input = [NSString stringWithFormat:@"%@:%@:%@", username, realm, password]; NSString *ha1 = [self md5HashFromString:ha1Input]; // 计算HA2: MD5(method:uri) NSString *ha2Input = [NSString stringWithFormat:@"%@:%@", method, uri]; NSString *ha2 = [self md5HashFromString:ha2Input]; // 生成随机cnonce和固定nc(nonce计数,首次请求用00000001) NSString *cnonce = [[NSUUID UUID].UUIDString substringToIndex:8]; NSString *nc = @"00000001"; // 计算最终response: MD5(HA1:nonce:nc:cnonce:qop:HA2) NSString *responseInput = [NSString stringWithFormat:@"%@:%@:%@:%@:%@:%@", ha1, nonce, nc, cnonce, qop, ha2]; NSString *response = [self md5HashFromString:responseInput]; return response; }
3. 完善你的请求代码
先修正原代码里的小问题:GET请求通常不需要设置HTTPBody,如果服务端要求传递用户名密码,建议用URL参数或者改成POST请求。下面是优化后的完整流程代码:
初始请求(获取认证参数)
- (void)initiateAuthenticationFlow { NSString *username = @"your_real_username"; NSString *password = @"your_real_password"; NSString *serviceURLString = @"https://your-target-webservice.com"; NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:serviceURLString] cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:10.0]; [request setHTTPMethod:@"GET"]; NSURLSession *session = [NSURLSession sharedSession]; NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { if (error) { NSLog(@"初始请求错误: %@", error); return; } NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response; NSString *wwwAuthenticateHeader = httpResponse.allHeaderFields[@"Www-Authenticate"]; if (!wwwAuthenticateHeader) { NSLog(@"服务端未返回认证所需的Www-Authenticate头"); return; } // 解析头参数,发起认证请求 NSDictionary *authParams = [self parseWwwAuthenticateHeader:wwwAuthenticateHeader]; [self sendAuthenticatedRequestWithParams:authParams username:username password:password serviceURL:serviceURLString]; }]; [dataTask resume]; }
认证后的后续请求
- (void)sendAuthenticatedRequestWithParams:(NSDictionary *)authParams username:(NSString *)username password:(NSString *)password serviceURL:(NSString *)serviceURL { NSString *realm = authParams[@"realm"]; NSString *nonce = authParams[@"nonce"]; NSString *qop = authParams[@"qop"] ?: @"auth"; // 默认使用auth模式 // 生成摘要响应值 NSString *response = [self generateDigestResponseWithUsername:username password:password realm:realm nonce:nonce uri:serviceURL method:@"GET" // 匹配你的请求方法 qop:qop]; // 构建Authorization请求头 NSString *cnonce = [[NSUUID UUID].UUIDString substringToIndex:8]; NSString *authHeader = [NSString stringWithFormat:@"Digest username=\"%@\", realm=\"%@\", nonce=\"%@\", uri=\"%@\", qop=%@, nc=00000001, cnonce=\"%@\", response=\"%@\"", username, realm, nonce, serviceURL, qop, cnonce, response]; // 创建认证请求 NSMutableURLRequest *authenticatedRequest = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:serviceURL] cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:10.0]; [authenticatedRequest setHTTPMethod:@"GET"]; [authenticatedRequest setValue:authHeader forHTTPHeaderField:@"Authorization"]; // 发起请求并处理结果 NSURLSession *session = [NSURLSession sharedSession]; NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:authenticatedRequest completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { if (error) { NSLog(@"认证请求失败: %@", error); return; } NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response; if (httpResponse.statusCode == 200) { // 请求成功,解析返回数据 NSError *jsonError = nil; NSDictionary *responseDict = [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonError]; if (responseDict) { NSLog(@"认证成功,返回数据: %@", responseDict); } } else { NSLog(@"认证未通过,状态码: %ld", (long)httpResponse.statusCode); } }]; [dataTask resume]; }
注意事项
- 确保服务端遵循RFC 2617规范,返回的
Www-Authenticate头包含realm、nonce等必要参数 - 如果服务端要求
qop=auth-int,需要修改HA2的计算逻辑(加入请求体的哈希值) - 生产环境建议使用自定义NSURLSession配置,而非共享的
sharedSession,便于管理会话生命周期
内容的提问来源于stack exchange,提问作者shakshi
相关产品推荐
相关产品推荐

