iOS实现HTTP认证之摘要认证-Digest
什么是Digest?
摘要访问认证是一种协议规定的Web服务器用来同网页浏览器进行认证信息协商的方法。它在密码发出前,先对其应用哈希函数,这相对于HTTP基本认证发送明文而言,更安全。 从技术上讲,摘要认证是使用随机数来阻止进行密码分析的MD5加密哈希函数应用.
1. 基本流程
- 客户端发起GET(PUT、POST、DELETE...)请求
- 服务器响应401 Unauthorized,WWW-Authenticate指定认证算法,realm指定安全域
- 客户端重新发起请求,Authorization指定用户名和密码信息
- 服务器认证成功,响应200,可选Authentication-Info 如下图所示
2.basic和digest的区别
- basic 将“用户名:密码”打包并采用Base-64编码。(提示:base64是可以直接解码的) 缺点:密码很容易被窥探,可以挟持编码后的用户名、密码信息,然后发给服务器进行认证;可以与SSL配合,隐藏用户名密码。 如果你使用的是AFNetworking网络框架只需要调用如下代码就能实现
[_httpManager.requestSerializer setAuthorizationHeaderFieldWithUsername:@"username" password:@"password"];
复制代码 - digest 和basic不同的是,digest不以明文发送密码,在请求接口后服务器响应返回随机字符串nonce,而客户端发送响应摘要 response=MD5(HA1:nonce:HA2),其中HA1=MD5(username:realm:password), HA2=MD5(method:digestURI) 在HTTP 摘要认证中使用 MD5 加密是为了达成"不可逆"。 具体的加密方式不止MD5一种还有SHA256等加密方式,but计算公式是一样的
- digest参数介绍 username: 用户名(一般是规定好的) password: 密码(同上) realm: 服务器返回的realm,一般是域名 method: 请求的方法 nonce: 服务器发给客户端的随机的字符串 nc(nonceCount):请求的次数,用于标记,计数,防止重放攻击 cnonce(clinetNonce): 客户端发送给服务器的随机字符串 qop: 保护质量参数,一般是auth,或auth-int,这会影响摘要的算法 uri: 请求的uri(只是path) response: 客户端根据算法算出的摘要值
3.具体操作流程
第一步
- 首先我们需要做的是需要下载一些实用工具
- 接口请求工具-postman
- 抓包工具-Fiddler、Wireshark、Charles (选其一即可)
第二步
- 配置postman设置,如下图注:Algorithm 选择自己需要的加密方式
第三步
- 打开抓包工具准备抓取请求
- 点击postman工具中的蓝色按钮“send”发送请求
第四步
- 查看抓包工具中的信息,如图
- 标红的部分 WWW-Authenticate: Digest realm="xxx.com",nonce="dcd1e62391252a6fae88fbec4485a32b", algorithm=SHA-256 我们需要的做的就是把nonce字符串在下次请求的时候在header头部设置里传给服务器校验,如果校验成功就会返回200
- 看一下200的那条数据返回,如图
- 看一下Headers返回的数据 Authorization: Digest username="ApiAdmin", realm="xxx.com", nonce="dcd1e62391252a6fae88fbec4485a32b", uri="xxxx?ChannelId=101", algorithm="SHA-256", response="c7ba2951cb51d32c1764e368813c139e8a4364807f7f2764ab7e1130341087fb" 这是一次正常的数据操作流程
第五步
- 最重要的来了,那就是代码实现
- 入坑经历,最重要!最重要!最重要!
- 我项目中的一直使用的是AFNetworking网络库请求接口,我花费了将近一天的时间调试一直处于401状态码返回,也就是说一直没有调通!
- 脱坑技巧,改用系统原生的的网络请求! 注:如果你使用AFNetworking网络框架并且成功实现,拜托一定要留言板留下你的代码实现,表示感谢!
第六步
代码实现 *注:nonceStr 声明一个全局的NSString来接收请求的nonce
-(void)getRequest{
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:url]];
//以GET请求为例,以下方法为封装的配置方法
[self setHttpHeaderDigestURI:identifier requestType:RequestType_GET nonce:self.nonceStr]
; NSURLSession *session = [NSURLSession sharedSession]; //发送请求 NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:self.request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { NSDictionary * pFieldd =[(NSHTTPURLResponse*)response allHeaderFields]; NSInteger statusCode =[(NSHTTPURLResponse*)response statusCode]; if (statusCode == 401) {//digest auth NSString * strAuthenticate = [pFieldd valueForKey:@"Www-Authenticate"]; if ([strAuthenticate containsString:@"nonce="]) { NSArray *arr = [strAuthenticate componentsSeparatedByString:@"nonce="]; NSArray *newArr = [[arr lastObject] componentsSeparatedByString:@","]; NSMutableString *newStr = [NSMutableString stringWithString:[newArr firstObject]] ; if (newStr.length >= 3) { [newStr replaceCharactersInRange:NSMakeRange(0, 1) withString:@""]; [newStr replaceCharactersInRange:NSMakeRange(newStr.length - 1, 1) withString:@""]; } NSString *nonce = newStr ; weakself.nonceStr = nonce; weakself.count += 1; if (weakself.count > 3) { weakself.count = 0;
[weakself error:error finishedBlock:finishedBlock]
; }else{
[weakself sendGetRequestWithParams:params getValues:values resultType:type finishedBlock:finishedBlock]
; } }else{
[weakself error:error finishedBlock:finishedBlock]
; } }else{ NSError *error; NSMutableDictionary *responseObject = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:&error]; if (responseObject && [responseObject isKindOfClass:[NSDictionary class]]) { if (finishedBlock) { finishedBlock(CODE_JSON_OK,responseObject); NSError *error; NSData *jsonData = [NSJSONSerialization dataWithJSONObject:responseObject options:NSJSONWritingPrettyPrinted error:&error]; NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]; } }else{ if (finishedBlock) { if (responseObject != nil) { finishedBlock(CODE_ERROR_JSON, responseObject); }else{ finishedBlock(CODE_ERROR_JSON, @"responseObject为nil"); } } } } }]; //执行任务 [dataTask resume]; }复制代码
//配置请求的header
-(void)setHttpHeaderDigestURI:(NSString *)digestURI requestType:(RequestType)requestType nonce:(NSString *)nonce{
NSString *username = @"ApiAdmin";
NSString *password = @"xxx";
NSString *realm = @"xxx.com";
NSString *method ;
switch (requestType) {
case RequestType_POST:{
method = @"POST";
}
break;
case RequestType_GET:{
method = @"GET";
}
break;
case RequestType_PUT:{
method = @"PUT";
}
break;
case RequestType_DELETE:{
method = @"DELETE";
}
break;
default:
break;
}
//修改请求方法
self.request.HTTPMethod = method;
//此字符串可任意
nonce = @"9e6146023a70bdb0b4d1da795a029990";
if (nonce.length > 0) {
nonce = self.nonceStr;
self.nonceStr = nil;
}
//这里是SHA256加密如果你是MD5或者其他的可自由切换
NSString *HA1 = [[NSString stringWithFormat:@"%@:%@:%@",username,realm,password] SHA256];
NSString *HA2 = [[NSString stringWithFormat:@"%@:%@",method,digestURI] SHA256];
NSString *sha265 = [NSString stringWithFormat:@"%@:%@:%@",HA1,nonce,HA2];
NSString *algorithm = @"SHA-256";
NSString *response = [sha265 SHA256];
NSString *authorization = [NSString stringWithFormat:@"Digest username=\"%@\", realm=\"%@\", nonce=\"%@\", uri=\"%@\", algorithm=\"%@\", response=\"%@\"",username,realm,nonce,digestURI,algorithm,response];
[self.request setValue:authorization forHTTPHeaderField:@"Authorization"]
;
[self.request setValue :@"application/soap+xml;charset=utf-8" forHTTPHeaderField:@"Content-Type" ]
; }复制代码
顺便送上SHA256加密的方法,新建一个NSString的category,实现方法如下
#import "NSString+SHA256.h"
#import <CommonCrypto/CommonDigest.h>
@implementation NSString (SHA256)
- (NSString *)SHA256
{
const char *s = [self cStringUsingEncoding:NSUTF8StringEncoding];
NSData *keyData = [NSData dataWithBytes:s length:strlen(s)];
uint8_t digest[CC_SHA256_DIGEST_LENGTH] = {0};
CC_SHA256(keyData.bytes, (CC_LONG)keyData.length, digest);
NSData *out = [NSData dataWithBytes:digest length:CC_SHA256_DIGEST_LENGTH];
const unsigned *hashBytes = [out bytes];
NSString *hash = [NSString stringWithFormat:@"%08x%08x%08x%08x%08x%08x%08x%08x",
ntohl(hashBytes[0]), ntohl(hashBytes[1]), ntohl(hashBytes[2]),
ntohl(hashBytes[3]), ntohl(hashBytes[4]), ntohl(hashBytes[5]),
ntohl(hashBytes[6]), ntohl(hashBytes[7])];
return hash;
}
看到这里你基本上已经明白什么是Digest 和 怎么实现了。 感谢