You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 09:33:25