iOS 实现自动登录(从低级做法到高级做法)

9,903 阅读9分钟

建议先大致看一遍再斟酌用哪种方法


前言

最近在重构App。旧的版本登录模块没有自动登录功能,体验极其不好。网上搜索了很多教程都没找到完整的,故写篇文章梳理一下。

  • 难点一 审核问题 如果App首次启动后,加载的是登录界面,并且必须要登录才能进行其他操作,那么审核将会是个问题。本文不涉及这方面的知识(反正这App打包工程师是FAFA,留给他头疼)。网上涉及这方面的文章挺多的,之前粗略了解过,最好提供测试账号,并且提供测试视频到美国能直接看的视频网站。
  • 难点二 业务逻辑问题。 有两种实现方法。 第一种比较低级的。每次App启动都进入登录界面,如果之前设置了自动登录,就调用登录按钮的方法。实现起来比较简单。 第二种是仿WX的,也是现在的主流App采用的。如果之前登录成功过并且没有退出账号,那么下次打开App就进入功能主界面,不进入登录界面。 还有用来记录登录状态的flag状态的改变(最后总结说)。
  • 难点三 👆第二种方法中的登录令牌 token token是用来判断当前用户的登录状态!
  • 难点四 本地密码加密(最后发现自动登录可以不需要保存密码)

先不考虑加密 用UserDefault保存

Demo链接 https://github.com/Hsusue/iOS-AutoLogin 该Demo基于第二种方法,包含了界面逻辑、加密演示、token工具包。 第一种差不多且比较简单,以下会贴出代码。

演示自动登录

第一种low的方法

  • HSUUserDefault.m封装了UserDefault的方法
+(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];
}
  • AppDelage.m中 ,设置根控制器为登录控制器
- (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;
}
  • 登录控制器中 根据之前的设置 布置UI 若自动登录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;
}
  • 在注销账号的方法中 自动登录设为NO 切换程序根控制器
        [HSUUserDefault saveUserDefaultObject:@(NO) key:kAutoLogin];
        
        AppDelegate *appDelegate = (AppDelegate *)[UIApplication sharedApplication].delegate;
        appDelegate.tabBarCtl = nil;
        LoginVC *loginVC = [[LoginVC alloc] init];
        appDelegate.window.rootViewController = loginVC;

第二种方法

  • 在appDelegate.m中 根据是否自动登录 设置程序根控制器
- (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中发起登录请求,但是会怪怪的。

// 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显示按钮 程序根控制器置回登录控制器
}

总结

  • 第一种实质上每次都会调用登录API,而第二种正规的做法则要根据token来判断发起什么请求。

2018.7.25更新

接下来了解一下token。因为要用到后台数据,无demo演示。

token是登录令牌,是用来判断当前用户的登录状态!

画张图举个例子 了解token的实现。

正规流程

当用户从设备A登录后,服务器通过某算法生成一个token,假设为1(实际上是很长的字符串),保存在数据库中并且返回这个值给A。A收到后,记录起来,从此发起(有需要此token的网络请求)都要带上这个token。如果用户从设备B登录,那么服务器会生成新的token(假设2),(不支持多设备登录的情况下)旧的token“1”废弃。如果A这时发起请求,服务器验证token,则可能会告诉A“此账号在别处登录”。

再具体了解token。

  • token具有时效性。 这个时效性是后台根据具体需要设置的。 像聊天的APP,时效性可以长达1年(猜测)。 像支付的APP,时效性就应该短点。 过期后,服务器接收到请求发现该token已失效,就告诉“发起端长时间未操作,请重新登录”。
  • token同时间内可以不唯一、拓展 考虑这种情况,某视频会员账号最多支持三个端登录。这时A、B、C能同时观看视频。如果这三个token还在有效期内,D这时登录,可能无法登录,或者顶掉一个在线设备(看后台怎么处理)。数据库的状态应该是存放有效的三个token。 QQ这种手机端、电脑端原理差不多。
  • token状态的改变 在别处登录、账号密码修改、过期,都会导致原token失败。这时候应该提醒用户,清空保存的token并退回登录界面。

使用token

步骤一 token的保存获取删除

对比以下两种方法。 在安全性来说,两者都能在未越狱手机直接导出。 在方便性来说,UserDefault简单点。 cookie好的地方就是能设置过期时间,能本地直接判断身份信息是否过期。

  • 方法一 最简单的保存到NSUserDefault中
  // 保存
  [userDefaults setObject:token forKey:@"token"];
  [userDefaults synchronize];
  // 获取
  [userDefaults objectForKey:@"token"];
  // 删除
  [userDefaults removeObjectForKey:@"token"];
  • 方法二 使用cookie cookie —— 储存在用户本地终端上的数据。 其实就是字典生成cookie的文件保存到App的包里。 如果App还接有其他WebView自动登录也是用这方法。

苹果已经帮我们封装好了,用到两个类。 NSHTTPCookie:将字典转成可识别cookie NSHTTPCookieStorage:存储NSHTTPCookie的对象 cookie要设置(本地的)过期时间,不然App关闭就会清除!

建议封装一个工具类用。 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.m

#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。

步骤二 请求中携带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。

疑惑的地方

  • 通过更改window.rootViewController 来切换登录控制器和主功能控制器是否不妥?以前做的项目是present出来的。但网上参考的文章是这样。
  • 保存token用cookie是不是有点小题大做?比起NSUserDefaults,优势只有一个本地过期属性。