iOS 實現自動登入(從低階做法到高階做法)

Hsusue發表於2018-08-02

建議先大致看一遍再斟酌用哪種方法


前言

最近在重構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,優勢只有一個本地過期屬性。

相關文章