最近在重构App。旧的版本登陆模块没有自动登陆功能,体验极其很差。网上搜索了不少教程都没找到完整的,故写篇文章梳理一下。ios
Demo连接 https://github.com/Hsusue/iOS-AutoLogin 该Demo基于第二种方法,包含了界面逻辑、加密演示、token工具包。 第一种差很少且比较简单,如下会贴出代码。 git
+(void)saveUserDefaultObject:(id)object key:(NSString *)key
{
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:object forKey:key];
[defaults synchronize];
}
+(id)getUserDefaultObject:(NSString *)key
{
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
id tempObject = [defaults objectForKey:key];
return tempObject;
}
+(void)removeObjectWithKey:(NSString *)key
{
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults removeObjectForKey:key];
[defaults synchronize];
}
复制代码
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
self.window.backgroundColor = [UIColor whiteColor];
LoginVC *vc = [[LoginVC alloc] init];
self.window.rootViewController = vc;
[self.window makeKeyAndVisible];
return YES;
}
复制代码
- (void)viewDidLoad {
self.userNameTextField.text = [HSUUserDefault getUserDefaultObject:kUserName];
BOOL isRemember = [[HSUUserDefault getUserDefaultObject:kRememberPassword] boolValue];
self.rememberSwitch.on = isRemember;
if (isRemember) {
self.psdTextField.text = [HSUUserDefault getUserDefaultObject:kUserPassword];
}
BOOL isAutoLogin = self.autoLoginSwitch.on;
if (isAutoLogin) {
[self loginBtnClick];
}
}
复制代码
- (void)loginSuccess {
[HSUUserDefault saveUserDefaultObject:self.userNameTextField.text key:kUserName];
BOOL isAutoLogin = self.autoLoginSwitch.on;
if (isAutoLogin) {
[HSUUserDefault saveUserDefaultObject:@(YES) key:kAutoLogin];
[HSUUserDefault saveUserDefaultObject:@(YES) key:kRememberPassword];
[HSUUserDefault saveUserDefaultObject:self.psdTextField.text key:kUserPassword];
} else { // 不自动登陆
[HSUUserDefault saveUserDefaultObject:@(NO) key:kAutoLogin];
BOOL isRememberPsd = self.rememberSwitch.on;
if (isRememberPsd) { // 记住密码
[HSUUserDefault saveUserDefaultObject:@(YES) key:kRememberPassword];
[HSUUserDefault saveUserDefaultObject:self.psdTextField.text key:kUserPassword];
} else {
[HSUUserDefault saveUserDefaultObject:@(NO) key:kRememberPassword];
[HSUUserDefault saveUserDefaultObject:nil key:kUserPassword];
}
}
// 切换AppDelegate的控制器
HSUTabBarController *tabBarController = [[HSUTabBarController alloc] init];
AppDelegate *appDelegate = (AppDelegate *)[UIApplication sharedApplication].delegate;
appDelegate.tabBarCtl = tabBarController;
appDelegate.window.rootViewController = appDelegate.tabBarCtl;
}
复制代码
[HSUUserDefault saveUserDefaultObject:@(NO) key:kAutoLogin];
AppDelegate *appDelegate = (AppDelegate *)[UIApplication sharedApplication].delegate;
appDelegate.tabBarCtl = nil;
LoginVC *loginVC = [[LoginVC alloc] init];
appDelegate.window.rootViewController = loginVC;
复制代码
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
self.window.backgroundColor = [UIColor whiteColor];
BOOL isAutoLogin = [[HSUUserDefault getUserDefaultObject:kAutoLogin] boolValue];
if (isAutoLogin) { // 自动登陆 进入主界面
self.tabBarCtl = [[HSUTabBarController alloc] init];
self.window.rootViewController = self.tabBarCtl;
} else { // 进入登陆界面
LoginVC *vc = [[LoginVC alloc] init];
self.window.rootViewController = vc;
}
[self.window makeKeyAndVisible];
return YES;
}
复制代码
先讲到这,这样看第二种方法是种伪自动登陆,固然能够在AppDelegate.m中发起登陆请求,可是会怪怪的。github
// AppDelegate.m
if (isAutoLogin) { // 自动登陆 进入主界面
self.tabBarCtl = [[HSUTabBarController alloc] init];
self.window.rootViewController = self.tabBarCtl;
[self.tabBarCtl autoLogin];
}
// HSUTabBarController.m中 实现API代理方法
- (void)loginSuccess {
// 这里就不须要设置自动登陆什么的了
// 处理返回的数据
}
- (void)loginFailure {// 密码错误什么的
// 设置自动登陆和记住密码为NO 密码为nil
// AlertCtl显示按钮 程序根控制器置回登陆控制器
}
复制代码
token是登陆令牌,是用来判断当前用户的登陆状态!算法
当用户从设备A登陆后,服务器经过某算法生成一个token,假设为1(其实是很长的字符串),保存在数据库中而且返回这个值给A。A收到后,记录起来,今后发起(有须要此token的网络请求)都要带上这个token。若是用户从设备B登陆,那么服务器会生成新的token(假设2),(不支持多设备登陆的状况下)旧的token“1”废弃。若是A这时发起请求,服务器验证token,则可能会告诉A“此帐号在别处登陆”。数据库
对比如下两种方法。 在安全性来讲,二者都能在未越狱手机直接导出。 在方便性来讲,UserDefault简单点。 cookie好的地方就是能设置过时时间,能本地直接判断身份信息是否过时。安全
// 保存
[userDefaults setObject:token forKey:@"token"];
[userDefaults synchronize];
// 获取
[userDefaults objectForKey:@"token"];
// 删除
[userDefaults removeObjectForKey:@"token"];
复制代码
苹果已经帮咱们封装好了,用到两个类。 NSHTTPCookie:将字典转成可识别cookie NSHTTPCookieStorage:存储NSHTTPCookie的对象 cookie要设置(本地的)过时时间,否则App关闭就会清除!bash
建议封装一个工具类用。 HSUCookieTool.h服务器
#import <Foundation/Foundation.h>
@interface HSUCookieTool : NSObject
/**
生成cookie
@param name cookie的名字
@param value cookie的值
@param domain 域名
*/
+ (void)saveCookieWithName:(NSString *)name value:(NSString *)value domain:(NSString *)domain;
/**
删除cookie
@param name cookie的名字
*/
+ (void)deleteCookieWithName:(NSString *)name;
/**
获取cookie
@param name cookie的名字
@return 对应的cookie,可能为空
*/
+ (NSHTTPCookie *)cookieWithName:(NSString *)name;
@end
复制代码
HSUCookieTool.mcookie
#import "HSUCookieTool.h"
@implementation HSUCookieTool
+ (void)saveCookieWithName:(NSString *)name value:(NSString *)value domain:(NSString *)domain{
// 保存
NSMutableDictionary *cookieProperties = [NSMutableDictionary dictionary];
// 给cookie取名
[cookieProperties setObject:name forKey:NSHTTPCookieName];
// 设置值
[cookieProperties setObject:value forKey:NSHTTPCookieValue];
// 存放目录 一般@"/"
[cookieProperties setObject:@"/" forKey:NSHTTPCookiePath];
// 设置本地过时时间 一年后
// 不设置关掉App就会清空
[cookieProperties setValue:[NSDate dateWithTimeIntervalSinceNow:3600*24*30*12] forKey:NSHTTPCookieExpires];
// 设置域名
[cookieProperties setObject:[NSURL URLWithString:domain].host forKey:NSHTTPCookieDomain];
// 生成cookie
NSHTTPCookie *httpCookie = [NSHTTPCookie cookieWithProperties:cookieProperties];
// 存入仓库
[[NSHTTPCookieStorage sharedHTTPCookieStorage] setCookie:httpCookie];
}
+ (void)deleteCookieWithName:(NSString *)name {
NSHTTPCookieStorage *cookieJar = [NSHTTPCookieStorage sharedHTTPCookieStorage];
for (NSHTTPCookie *cookie in [cookieJar cookies]) {
NSLog(@"cookie%@", cookie);
if ([cookie.name isEqualToString:name]) {
[cookieJar deleteCookie:cookie];
}
}
}
+ (NSHTTPCookie *)cookieWithName:(NSString *)name {
NSHTTPCookieStorage *cookieJar = [NSHTTPCookieStorage sharedHTTPCookieStorage];
for (NSHTTPCookie *cookie in [cookieJar cookies]) {
NSLog(@"cookie%@", cookie);
if ([cookie.name isEqualToString:name]) {
return cookie;
}
}
return nil;
}
@end
复制代码
注意的是,相同路径下再次保存相同cookie名字会替换掉以前的同名cookie。但仍是建议先删除再添加token。网络
API代理方法应该能设置请求头 在各API中设置请求头
- (AFHTTPRequestSerializer <AFURLRequestSerialization> *)requestSerializer {
// 获取本地的token
NSHTTPCookie *cookie = [HSUCookieTool cookieWithName:@"token"];
NSString *token = cookie.value;
// 假设后台规定@"AuthorisedToken"
AFHTTPRequestSerializer *requestSer = [AFHTTPRequestSerializer serializer];
[requestSer setValue:token forHTTPHeaderField:@"AuthorisedToken"];
return requestSer;
}
复制代码
研究完token发现,本地不须要记住密码也能实现自动登陆。WX也是这样。接口能够靠帐号和token实现,没密码什么事,毕竟不在本地保存密码比任何加密都来得安全。 但若是后台没有实现token,又要实现自动登陆,就只能每次调用登陆接口。这就不可避免要用到密码。
前面说过了NSUserDefaults和NSHTTPCookie,没越狱的手机能直接导出信息,让人没安全感。因此加密仍是有须要的。
方法一 经过Keychain保存密码 iOS的keychain服务提供了一种安全的保存私密信息(密码,序列号,证书等)的方式,每一个ios程序都有一个独立的keychain存储。 可是删除App后还存在,感受不太好。 实质就是换个安全的地方把密码保存起来。其使用很是容易,且有很多封装好的工具库。这里就不展开了。
方法二 使用算法加密 苹果自带的加密算法有不少,这里介绍Base64加密。
用NSString+encrypt实现
//给定一个字符串,对该字符串进行Base64编码,而后返回编码后的结果
- (NSString *)base64EncodeString {
//先把字符串转换为二进制数据
NSData *data = [self dataUsingEncoding:NSUTF8StringEncoding];
//对二进制数据进行base64编码,返回编码后的字符串
return [data base64EncodedStringWithOptions:0];
}
//对base64编码后的字符串进行解码
- (NSString *)base64DecodeString {
//1.将base64编码后的字符串『解码』为二进制数据
NSData *data = [[NSData alloc]initWithBase64EncodedString:self options:0];
//2.把二进制数据转换为字符串返回
return [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding];
}
复制代码
和后台一块儿规定一个加密算法,对网络请求的密码参数进行加密。。到了服务器后再解密。这样即便抓包密码也能不直接暴露出来。这就须要和后台商量好了。
我的认为普通App(安全性要求不高)最优自动登陆方法 1.AppDelegate中判断好加载主功能界面仍是登陆界面。 2.登陆成功后要设置自动登陆,用base64加密方法加密密码和token后保存起来。 3.处理好任何有关地方的业务逻辑。
业务逻辑 1⃣️保存帐号 2⃣️是否记住密码 3⃣️保存密码 4⃣️是否自动登陆 5⃣️保存token
登陆成功一定1⃣️5⃣️。若选择(记住密码和自动登陆),2⃣️3⃣️4⃣️。 被顶号或注销帐号要❌4⃣️, ❌5⃣️。 密码错误(忽然被修改), ❌2⃣️, ❌3⃣️, ❌4⃣️, ❌5⃣️。 token过时, ❌4⃣️, ❌5⃣️。 有token更新的地方记得更改本地token。