一、HTTP常用方法
- 在客户端和服务器之间进行请求-响应时,两种最常被用到的方法是:GET 和 POST。
- 浏览器演示GET/POST请求的区别。
- 控制台查看服务器访问日志,观察 GET 和 POST 请求的区别。
1、GET 请求
-
GET - 从指定的URL获取资源数据。
- GET 请求可被缓存,可以保留在浏览器历史记录中,可被收藏为书签。
- GET 请求从数学角度来讲,GET的结果是 幂等 的
- GET 请求有长度限制,在HTTP协议定义中,没有对GET请求的数据大小限制,不过因为浏览器不同,一般限制在2~8K。
- GET 请求的所有参数包装在URL中,并且服务器的访问日志会记录,不要传递敏感信息。
-
名词解析
- 幂等
- 在数学中,一个数多次进行该运算所得的结果和进行一次该运算所得的结果是一样的,那么我们就称该运算是幂等的。比如绝对值运算就是一个例子,在实数集中,有abs(a)=abs(abs(a))。
- GET的结果是 幂等 的,是说对同一个URL请求多次获得的结果都是一样的。
- 幂等
-
GET请求参数格式
- 在资源路径末尾添加 ? 表示追加参数
- 每一个变量及值按照 变量名=变量值 方式设定,不能包含空格或中文
- 多个参数使用 & 连接
- URL 字符中如果包含中文,需要添加百分号转义。
2、POST 请求
- POST - 向指定的资源提交要被处理的数据
- POST 请求不会被缓存,不会保留在浏览器历史记录中,不能被收藏为书签
- POST 向服务器发送数据,也可以获得服务器处理之后的结果,效率不如GET
- POST 提交数据比较大,大小由服务器的设定值限制,PHP通常限定2M。
- POST 提交的参数包装成二进制的数据体,格式与 GET 基本一致,只是不包含 ?
- URL中 只有资源路径,但不包含参数,服务器日志不会记录参数,相对更安全。
- 所有设计用户隐私的数据(密码,银行卡号)一定记住使用POST方式传递。
3、GET 缓存
1、提问:为什么 GET 请求可以被缓存,而 POST 请求不被缓存?
答:GET 的结果是幂等的,同一个 URL 每次的请求结果都是一样的,基于这个特点,GET 请求就可以被缓存起来避免每次对同一个 URL 请求都要访问服务器,可以节省用户流量和提供响应速度。而 POST请求的结果一般都是不一样的,比如用户登录,每个用户登录获得的信息都是不一样的,基于这个特点,POST 请求的结果不会被缓存。-
2、GET 缓存实现
-
Request缓存请求头
- If-None-Match : 与响应头的Etag相对应,可以判断本地缓存数据是否发生变化。
-
如何实现 get缓存?
- 请求是可变的,缓存策略要每次都从服务器加载
- 每次得到响应后,需要记录 etag
- 下次发送请求的同时,将 etag 一起发送给服务器,由服务器比较内容有没有变
代码实现如下
-
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
NSURL *url = [NSURL URLWithString:@"[http://localhost/itcast/images/head1.png](http://localhost/itcast/images/head1.png)"];
// NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:15.0];
// 传递 etag
if (self.etag.length > 0) {
[request setValue:self.etag forHTTPHeaderField:@"If-None-Match"];
}
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
NSLog(@"%@,%zd",response,data.length);
// 类型转换(在oc中,如果将父类转换成子类需要强制转换)
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
// 获取并记录 etag,区分大小写
self.etag = httpResponse.allHeaderFields[@"Etag"];
// 判断响应状态码是否是 304 Not Modified
if (httpResponse.statusCode == 304) {
NSLog(@"加载本地数据");
// 如果是,使用本地缓存
// 根据请求获得被缓存的响应
NSCachedURLResponse *cacheResponse = [[NSURLCache sharedURLCache] cachedResponseForRequest:request];
// 拿到缓存的数据
data = cacheResponse.data;
}
NSLog(@"etag = %@",self.etag);
self.iconView.image = [UIImage imageWithData:data];
}];
}
- 3、代码小结
- 请求的缓存策略使用NSURLRequestReloadIgnoringCacheData,忽略本地缓存。
- 服务器响应结束后,要记录 Etag,服务器内容和本地缓存对比是否变化的重要依据。
- 在发送请求时,设置 If-None-Match,并且传人Etag。
- 连接结束后,要判断响应头的状态码,如果是304,说明本地缓存内容没有变化。
4、NSURLCache--设置缓存
- 在iOS中,可以使用NSURLCache类缓存数据
// 设置网络缓存
// 4M 的内存缓存
// 20M 的磁盘缓存
// diskPath:缓存路径 nil:表示使用系统默认的缓存路径:沙盒/Library/Caches
NSURLCache *cache = [[NSURLCache alloc] initWithMemoryCapacity:4 * 1024 * 1024 diskCapacity:20 * 1024 * 1024 diskPath:@"pkxing"];
// 设置全局缓存
[NSURLCache setSharedURLCache:cache];
- iOS 5之前:只支持内存缓存。
- 从iOS 5开始:同时支持内存缓存和硬盘缓存
# AFNetworking的作者Mattt说:无数开发者尝试自己做一个简陋而脆弱的系统来实现网络缓存功能,殊不知 NSURLCache 只要两行代码就能搞定且好上100倍。
二、用户登录
- 安全原则
- 不能在网络上传传输用户隐私数据的明文
- 不能在本地存储用户隐私数据的明文
1、GET登录
- (void)getLogin{
// 创建url
/**
url扩展:
1、login.php 负责登录的脚本,提示:上课使用的是 php,而工作中不一定,可能是.jsp,asp,aspx...取决于后台,后台提供什么,客户端就用什么。
2、‘?’ 表示接参数
3、参数格式:变量名=值
4、‘&’ 多个参数拼接
*/
NSString urlStr = [NSString stringWithFormat:@"http://localhost/login.php?username=%@&password=%@",self.userName,self.password];
// 如果url 字符串中,包含中文或空格等特殊字符,需要添加百分号转义
urlStr = [urlStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSURL *url = [NSURL URLWithString:urlStr];
// 创建请求
NSURLRequest *request = [NSURLRequest requestWithURL:url];
// 请求的默认方法就是 GET
// GET 效率高,使用频率高的访问 建议是用GET! POST一般用来发送订单,个人隐私数据,上传文件....
NSLog(@"%@",request.HTTPMethod);
// 发送请求
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:data options:0 error:NULL];
NSLog(@"%@,%@",response,dict);
}];
}
-
1.1、URL编码
-
为什么需要编码?
- url支持26个英文字母、数字和少数几个特殊字符,因此,对于url中包含非标准url的字符时,就需要对其进行编码。
Url编码通常也被称为百分号编码(Url Encoding,also known as percent- encoding),是因为它的编码方式非常简单,使用%百分号加上两位的字 符——0123456789ABCDEF——代表一个字节的十六进制形式。
Url编码默认使用的字符集是ASCII
-
1.2、如何编码
NSString urlStr = [NSString stringWithFormat:@"http://localhost/login.php?username=% @&password=%@",self.userName,self.password];
// 如果url 字符串中,包含中文或空格等特殊字符,需要添加百分号转义
urlStr = [urlStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
- 1.3、GET请求缓存的位置
-
验证 GET 请求返回的 json 数据被缓存起来了。
- 把请求对象的缓存策略改为:NSURLRequestReturnCacheDataDontLoad。只从缓存加载。再次运行程序看有没结果返回。
- 停止 apache 服务器:再次运行程序看有没结果返回。
- 停止命令:sudo apachectl -k stop;
- 重启命令:sudo apachectl -k restart;
- 开启命令:sudo apachectl -k start;
-
GET请求返回的json数据缓存在Caches/bundleId/Cache.db的sqlite数据库中。
- 通过终端查看:进入到 Caches.db 所在的文件夹
- 执行命令 sqlite3 Cache.db; 打开数据库
- 执行命令 .tables; 可查看数据库的表
- 执行命令 select * from 表名;可查看对应的表数据,即缓存内容
-
友情提示
- 现在只需要知道缓存的位置就行了,后续的课程会介绍数据库相关的知识。
-
2、POST登录
- 代码实现
- (void)postLogin{
// 创建url,不需要添加百分号转义(内部已经实现)
NSURL *url = [NSURL URLWithString:@"http://localhost/login.php"];
// 创建请求
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
// 设置请求方法为 POST
request.HTTPMethod = @"POST";
// 设置请求体二进制数据
NSString *bodyStr = [NSString stringWithFormat:@"username=%@&password=%@",self.userName,self.password];
// request.HTTPBody = [bodyStr dataUsingEncoding:NSUTF8StringEncoding];
request.HTTPBodyStream = [[NSInputStream alloc] initWithData:[bodyStr dataUsingEncoding:NSUTF8StringEncoding]];
// 发送请求
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:data options:0 error:NULL];
NSLog(@"%@,%@",response,dict);
}];
}
-
如何查看请求体的格式?
- 可以借助火狐浏览器和firebug插件。获得使用浏览器登录时要发送请求体的格式。
- 火狐浏览器几乎是每一个做网络开发的程序员必备的一款神器。它有两个显著的特点:
- 性能很差,速度很慢。
- 插件非常多。各种调试插件。
-
设置请求体注意点
- HTTPBody 和 HTTPBodyStream 只需要设置其中一个就行了,如果两个都设置了,前面设置的就无效了。
3、GET和POST 请求对比
-
URL 对比
-
GET
- login.php 负责登录的脚本。
- 提示:上课使用的是 php,而工作中不一定,可能是.jsp,asp,aspx...取决于后台,后台提供什么,客户端就用什么。
- ‘?’ 表示接参数。参数格式:变量名=值。
- ‘&’表示 多个参数拼接。
- 包含中文或空格等特殊字符,需要添加百分号转义。
- login.php 负责登录的脚本。
-
POST
- 只有一个 URL,不包含参数。
-
-
Request 对比
- GET:默认方法就是 GET。
- POST
- 将字符串转换成二进制数据,设置HTTPBody或HTTPBodyStream
- 指定 request.HTTPMethod = @"POST"
-
Connnection 对比
- 就是将请求发送给服务器,获得二进制数据的响应、 GET和POST没有区别。
三、模拟登录
1、搭建界面
-
友情提示和细节处理
- 在移动应用中,绝大多数的登录界面不需要记住登录密码控件。
- 在使用自动布局时,如果一组相关的控件包在一个大的控件中,只需要对外面大的控件添加布局约束就可以了。
- 根据传统网页应用程序的特点,用户按回车键时,下一个文本输入框应该获得焦点。
-
实现登录逻辑
- 连线获得用户名和密码文本输入框。
- 监听登录按钮的点击。
- 使用 post 请求登录
2、保存登录信息
- 登录成功后保存用户名和密码。
- 程序启动的时候加载用户名和密码。
#define HMUsernameKey @"HMUsernameKey"
#define HMPasswordKey @"HMPasswordKey"
- 保存用户登录信息
- (void)saveUserInfo{
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:self.nameField.text forKey:HMUsernameKey];
[defaults setObject:self.passField.text forKey:HMPasswordKey];
}
- 加载用户登录信息
- (void)loadUserInfo{
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
self.nameField.text = [defaults valueForKey:HMUsernameKey];
self.passField.text = [defaults valueForKey:HMPasswordKey];
}
- 友情提示
在 iOS8.0 之后,就不需要同步了。
四、Base64
1、简介
参考网站:http://zh.wikipedia.org/wiki/Base64
- 是网络上使用最广泛的编码系统,能够将任何二进制数据,转换成只有65个字符组成的文本文件。
- 由 az,AZ,0~9,+,/,= 等65个字符组成。
- Base64 编码后的结果能够反算,不够安全。
- Base64 是所有现代加密算法的基础算法。
2、原理
参考文章:http://www.cnblogs.com/hongru/archive/2012/01/14/2321397.html
- base64的编码都是按字符串长度,以每3个8bit的字符为一组,
- 然后针对每组,首先获取每个字符的ASCII编码,
- 然后将ASCII编码转换成8bit的二进制,得到一组3*8=24bit的字节
- 然后再将这24bit划分为4个6bit的字节,并在每个6bit的字节前面都填两个高位0,得到4个8bit的字节
- 然后将这4个8bit的字节转换成10进制,对照Base64编码表,得到对应编码后的字符。
注:如果被编码字符长度不是3的倍数的时候,则都用0代替,对应的输出字符为=
ABC
01000001 01000010 01000011
010000 010100 001001 000011
16 20 9 3
QUJD
AB
01000001 01000010
010000 010100 001000
16 20 8
QUI=
A
010000 010000
16 16
QQ==
Base64对照表
3、终端命令
# 将 abc.png 进行 base64编码,生成 xxx.txt 文件
$ base64 abc.png -o xxx.txt // -o 表示输出
# 将 xxx.txt 解码生成1.png
$ base64 -D xxx.txt -o 1.png // -D 表示 decoder 解码
# 将字符串 ABC 进行 base64 编码
$ echo -n ABC | base64
# 将字符串 QUJD 解码
$ echo -n QUJD | base64 -D
4、Base64代码实现
- 1、修改代码
- 保存密码不能使用明文,使用base64进行加密。
- 提交数据到服务器不能使用明文,使用base64进行加密。
#pragma mark - Base64
// 编码:A => QQ==
-(NSString *)base64Encode:(NSString *)string{
// 1.将字符串转换成二进制数据
NSData *data = [string dataUsingEncoding:NSUTF8StringEncoding];
// 2.利用 ios7.0的方法,直接 base64 编码
return [data base64EncodedStringWithOptions:0];
}
// 解码:QQ== => A
- (NSString *)base64Decode:(NSString *)string {
// 1.将 base64编码后的字符串,解码成二进制数据
// 这里不能使用注释掉的方法转换成二进制,因为 string 就是已经编码过的字符串
// NSData *data = [string dataUsingEncoding:NSUTF8StringEncoding];
NSData *data = [[NSData alloc] initWithBase64EncodedString:string options:0];
// 2.返回解码的字符串
return [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
}
五、MD5加密
1、MD5是什么
Message Digest Algorithm MD5(中文名为消息摘要算法第五版)是计算机安全领域广泛使用的一种散列函数(也叫Hash函数),用以提供消息的完整性保护。其核心思想是从给定的数据中提取特征码,不容产生重复。加密后的字符串通常被称为指纹或消息摘要。 32位
知识扩展:特征码有什么作用?
举例:百度网盘
上传文件到百度网盘的时候,有时候会发现上传速度非常快,几秒钟几个 G 的文件就上传上去了,其内部实现的原理是根据给每个文件一个特征码来实现的,百度服务器会为每一个上传的文件生成一个特征码,后续用户上传文件时会检测网盘里面是否已经有相同特征码的文件,如果有,就直接拿过来用了,相当于引用计数加一,如果有人删除了,则引用计数减一,知道所有人都删除了,文件才会真正从百度服务器删掉。
2、MD5算法特点
- 压缩性:任意长度的数据,算出的MD5值长度都是固定的。相同的字符串,每次MD5后的结果是固定的。都是32个字符
- 容易计算:从原数据计算出MD5值很容易。
- 抗修改性:对原数据进行任何改动,哪怕只修改1个字节,所得到的MD5值都有很大区别。
- 弱抗碰撞:已知原数据和其MD5值,想找到一个具有相同MD5值的数据(即伪造数据)是非常困难的。
- 强抗碰撞:想找到两个不同的数据,使它们具有相同的MD5值,是非常困难的。
- 不可逆性:不能逆运算。不可破解。
3、MD5的作用
-
一致性验证
- 我们都知道,地球上任何人都有自己独一无二的指纹,这常常成为司法机关鉴别罪犯身份最值得信赖的方法。与之类似,通过MD5就可以为任何文件(不管其大小、格式、数量)产生一个独一无二的"数字指纹",如果任何人对文件做了任何改动,其MD5值也就是对应的"数字指纹"都会发生变化。
- 具体来说文件的MD5值就像是这个文件的“数字指纹”。每个文件的MD5值是不同的,如果任何人对文件做了任何改动,其MD5值也就是对应的“数字指纹”就会发生变化。比如下载服务器针对一个文件预先提供一个MD5值,用户下载完该文件后,用这个算法重新计算下载文件的MD5值,通过比较这两个值是否相同,就能判断下载的文件是否出错,或者说下载的文件是否被篡改了。
- 利用MD5算法来进行文件校验的方案被大量应用到软件下载站、论坛数据库、系统文件安全等方面。
-
数字签名
- MD5的典型应用是对一段Message(字节串)产生fingerprint(指纹)以防止被“篡改”。
举个例子:
你将一段话写在一个叫readme.txt文件中,并对这个readme.txt产生一个MD5的值并记录在案,然后你可以传播这个文件给别人,别人如果修改了文件中的任何内容,你对这个文件重新计算MD5时就会发现(两个MD5值不相同)。如果再有一个第三方的认证机构,用MD5还可以防止文件作者的“抵赖”,这就是所谓的数字签名应用。
- MD5的典型应用是对一段Message(字节串)产生fingerprint(指纹)以防止被“篡改”。
-
安全访问认证
- 典型案例:加密用户登录密码。
当用户登录的时候,系统把用户输入的密码进行MD5 Hash运算,然后再去和保存在文件系统中的MD5值进行比较,进而确定输入的密码是否正确。通过这样的步骤,系统在并不知道用户密码的明码的情况下就可以确定用户登录系统的合法性。这可以避免用户的密码被具有系统管理员权限的用户知道。
- 典型案例:加密用户登录密码。
4、MD5加密实现
- 代码实现
NSString *password = self.passField.text.md5String; // 执行一次 MD5
NSString *password = self.passField.text.md5String.md5String; // 执行两次 MD5
对密码进行 MD5 加密 - 不安全
-
如何使MD5加密更安全?
- 加盐、现在用的比较少,前两年用得比较多。
// 准备盐
static NSString *salt = @"fadsfdbvcxweioa4321asfFAFA321DSFASDF%$%$^$^$$#@23123124{}{4";
NSString *password = [self.passField.text stringByAppendingString:salt].md5String;
温馨提示:‘盐’在现实生活中是佐料,就是给密码加点料,salt要够咸(复杂点的字符串)。
-
用HMac:HMAC运算利用哈希算法,以一个密钥和一个消息为输入,生成一个消息摘要作为输出。
NSString *password = [self.passField.text hmacMD5StringWithKey:@"itheima"];
上面代码 md5 的过程:使用密钥 itheima 对密码加密,加密后做md5,得到32位字符串,再次使用 itheima 加密,再md5。- HMAC现在使用的比较广泛,安全级别更高, 破解难度高。
- 但还是有风险:每次结果一致,有可能被暴力破解。
要想做到的安全级别更更高,现在密码学要求:同样的算法,同样的密码明文,每次的结果不一样。
5、生成带时间戳的密码
使用时间戳,目前使用非常广泛
- 使用客户端时间生成带时间戳的密码
- (NSString *)timePassword{
// 1.设置密钥 key
NSString *key = @"itheima".md5String;
// 2.使用密钥key对密码进行HMac
NSString *pwd = [self.passField.text hmacMD5StringWithKey:key];
NSLog(@"key = %@",key);
// 3.获得当前的系统时间
NSDateFormatter *fmt = [[NSDateFormatter alloc] init];
// 指定时区,真机通常需要指定时区
fmt.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"zh"];
// 设置时间格式
fmt.dateFormat = @"yyyy-MM-dd HH:mm";
// 格式化当前时间
NSString *dateStr = [fmt stringFromDate:[NSDate date]];
// 4.用密码 + 时间 生成 密码
pwd = [pwd stringByAppendingString:dateStr];
// 5.返回 hmac 结果
return [pwd hmacMD5StringWithKey:key];
}
NSString *password = [self timePassword];
- 使用服务器时间,生成带时间戳的密码
- (NSString *)timePassword{
// 1.设置密钥 key
NSString *key = @"itheima".md5String;
// 2.对密钥key对密码进行HMac
NSString *pwd = [self.passField.text hmacMD5StringWithKey:key];
// 3.获得当前服务器的系统时间
NSURL *url = [NSURL URLWithString:@"http://localhost/hmackey.php"];
// 使用同步获取时间(注意:这里要使用同步,确定先获得服务器的时间,后面的代码才能执行)
NSData *timeData = [NSData dataWithContentsOfURL:url];
// 反序列化取出时间
NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:timeData options:0 error:NULL];
NSString *dateStr = dict[@"key"];
// 4.用密码 + 时间 生成 密码
pwd = [pwd stringByAppendingString:dateStr];
// 5.返回 hmac 结果
return [pwd hmacMD5StringWithKey:key];
}
- 问题解答:
- 为什么要获得服务器时间来对密码进行hmac?
有些人是走在时间的前面的,他手机上的时间设置会比真实的时间的快5分钟。如果是这样的,就会导致客户端获得的系统时间和服务器获得的系统时 间相差几分钟。那就会导致 hmac 的结果不一致,无法登录。
- 为什么要获得服务器时间来对密码进行hmac?
6、其他
破解网站:http://www.cmd5.com
六、钥匙串访问
- 使用MD5加密本地密码有个问题
MD5是不可逆的,本地使用MD5加密密码保存到偏好设置的时候,无法再读取的时候载解析成 原来的文字。而使用 Base64加密又太过简单了,容易破解。这时候就要使用钥匙串了。
1、钥匙串简介
- 钥匙串访问,使用 AES 256加密算法,能够保证用户密码的安全。
- 钥匙串访问SDK,是苹果ios7.0.3 版本以后发布的
- 钥匙串访问的接口是纯C语言的,但是,网络上有个哥们把它封装成 OC的,使用相当简单!
- 钥匙串访问的第三方框架,是对C框架的封装,可以不用看源代码
- 框架地址:https://github.com/samsoffes/sskeychain
2、代码实现
- 保存用户登录信息
- (void)saveUserInfo{
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setValue:self.nameField.text forKey:HMUsernameKey];
// 保存到密码到钥匙串
/**
参数:
1.密码‘明文’,加密工作苹果做了,使用的是 AES 256 算法
2.服务名,可以随便写,建议使用bundleId
3.帐号,用户名。因为钥匙串访问中,可以保存很多帐号,很多app的密码
*/
NSString *bundleId = [NSBundle mainBundle].bundleIdentifier;
[SSKeychain setPassword:self.passField.text forService:bundleId account:self.nameField.text];
}
- 加载用户登录信息
- (void)loadUserInfo{
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
self.nameField.text = [defaults valueForKey:HMUsernameKey];
// 从钥匙串中获得密码
NSString *bundleId = [NSBundle mainBundle].bundleIdentifier;
self.passField.text = [SSKeychain passwordForService:bundleId account:self.nameField.text];
}
- 钥匙串访问的密码保存在哪里?
只有苹果知道,是为了进一步保障用户的密码安全。
七、重构登录代码
// 登录成功通知
#define HMLoginSuccessNotification @"HMLoginSuccessNotification"
@interface HMNetworkTools : NSObject
/**
* 全局访问点,用来获取单列对象
*/
+(instancetype)sharedTools;
/**
* 登录
*/
- (void)loginFailed:(void (^)())failed;
/**
* 用户名
*/
@property(nonatomic,copy) NSString *username;
/**
* 密码
*/
@property(nonatomic,copy) NSString *pwd;
@end
@implementation HMNetworkTools
/**
* 全局访问点,用来获取单列对象
*/
+(instancetype)sharedTools{
static id instance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[self alloc] init];
});
return instance;
}
- (instancetype)init {
if (self = [super init]) {
[self loadUserInfo];
}
return self;
}
/**
* 登录
*/
- (void)loginFailed:(void (^)())failed{
NSAssert(failed != nil, @"必须传入回调");
// 判断用户名或密码是否有值
if(!(self.username.length > 0 && self.pwd.length > 0)){
failed();
return;
}
NSString *password = [self timePassword];
NSLog(@"发送的密码=%@",password);
// 创建url
NSURL *url = [NSURL URLWithString:@"http://localhost/loginhmac.php"];
// 创建请求
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url ];
// 设置请求方法为 POST
request.HTTPMethod = @"POST";
// 设置请求体二进制数据
NSString *bodyStr = [NSString stringWithFormat:@"username=%@&password=%@",_username,password];
request.HTTPBodyStream = [[NSInputStream alloc] initWithData:[bodyStr dataUsingEncoding:NSUTF8StringEncoding]];
// 发送请求
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:data options:0 error:NULL];
if([dict[@"userId"] intValue] > 0) {
// 登录成功,保存用户信息
[self saveUserInfo];
// 发送通知
[[NSNotificationCenter defaultCenter] postNotificationName:HMLoginSuccessNotification object:@"Main"];
} else {
failed();
}
}];
}
#pragma mark - 私有方法
/**
* 生成带时间戳的密码
*/
- (NSString *)timePassword{
// 1.设置密钥 key
NSString *key = @"itheima".md5String;
// 2.对密钥key对密码进行HMac
NSString *pwd = [self.pwd hmacMD5StringWithKey:key];
// 3.获得当前服务器的系统时间
NSURL *url = [NSURL URLWithString:@"http://localhost/hmackey.php"];
// 使用同步获取时间
NSData *timeData = [NSData dataWithContentsOfURL:url];
// 反序列化取出时间
NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:timeData options:0 error:NULL];
NSString *dateStr = dict[@"key"];
// 4.用密码 + 时间 生成 密码
pwd = [pwd stringByAppendingString:dateStr];
// 5.返回 hmac 结果
return [pwd hmacMD5StringWithKey:key];
}
#pragma mark - 保存和加载用户信息
#define HMUsernameKey @"HMUsernameKey"
#define HMPasswordKey @"HMPasswordKey"
/**
* 保存用户登录信息
*/
- (void)saveUserInfo{
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:self.username forKey:HMUsernameKey];
NSString *bundleId = [NSBundle mainBundle].bundleIdentifier;
[SSKeychain setPassword:self.pwd forService:bundleId account:self.username];
}
/**
* 加载用户登录信息
*/
- (void)loadUserInfo{
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
self.username = [defaults valueForKey:HMUsernameKey];
// 从钥匙串中获得密码
NSString *bundleId = [NSBundle mainBundle].bundleIdentifier;
self.pwd = [SSKeychain passwordForService:bundleId account:self.username];
}