It is recommended to look it over before deciding which method to use


preface

App is being reconfigured recently. The old version of the login module did not have automatic login function and the experience was extremely poor. Online search a lot of tutorials did not find a complete, so write an article to comb.

  • If the login interface is loaded after the App is started for the first time, and you must log in to perform other operations, then the audit will be a problem. This article does not involve this aspect of knowledge (anyway, the App packaging engineer is FAFA, leaving him a headache). There are many articles related to this aspect on the Internet. I have had a rough understanding of it before. It is best to provide a test account and provide a test video to a video website that can be directly watched in the United States.
  • Difficulty 2: Business logic. There are two ways to do this. The first one is the lower one. Every time the App starts, the login interface will be entered. If automatic login is set before, the method of login button will be called. It’s relatively simple to implement. The second is a copy of WX, which is also used by mainstream apps. If you have logged in successfully before and have not logged out, the next time you open the App, you will enter the main interface of the function and not enter the login interface. There is also a change in the status of the flag used to record login status (finally summarized).
  • 👆 Login token in the second method Token is used to determine the login status of the current user!
  • Difficulty 4 Local password encryption (finally found that automatic login does not need to save the password)

Forget encryption and save with UserDefault

Demo link https://github.com/Hsusue/iOS-AutoLogin the Demo is based on the second approach, contains the interface logic, encryption, demonstration, token kit. The first is more or less straightforward, and the code is posted below.

The first method is low

  • Hsuuserdefault. m encapsulates the UserDefault method
+(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];
}
Copy the code
  • In appdelage. m, set the root controller as the login controller
- (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;
}
Copy the code
  • UI layout in the login controller based on previous Settings If automatic login YES, the login button method is triggered
- (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]; }}Copy the code
  • Save data switcher root controller in successful login callback method
- (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// remember password [HSUUserDefault saveUserDefaultObject:@(YES) key: krememberpsd]; [HSUUserDefault saveUserDefaultObject:self.psdTextField.text key:kUserPassword]; }else{ [HSUUserDefault saveUserDefaultObject:@(NO) key:kRememberPassword]; [HSUUserDefault saveUserDefaultObject:nil key:kUserPassword]; HSUTabBarController = [[HSUTabBarController alloc] init]; AppDelegate *appDelegate = (AppDelegate *)[UIApplication sharedApplication].delegate; appDelegate.tabBarCtl = tabBarController; appDelegate.window.rootViewController = appDelegate.tabBarCtl; }Copy the code
  • Automatic login in the logout method set to NO to switch the application root controller
        [HSUUserDefault saveUserDefaultObject:@(NO) key:kAutoLogin];
        
        AppDelegate *appDelegate = (AppDelegate *)[UIApplication sharedApplication].delegate;
        appDelegate.tabBarCtl = nil;
        LoginVC *loginVC = [[LoginVC alloc] init];
        appDelegate.window.rootViewController = loginVC;
Copy the code

The second way

  • Set the application root controller in Appdelegate. m based on whether to automatically log in
- (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; }elseLoginVC *vc = [[LoginVC alloc] init]; self.window.rootViewController = vc; } [self.window makeKeyAndVisible];return YES;
}
Copy the code

I’ll leave you there, but the second method is a pseudo-automatic login. You can make a login request in Appdelegate.m, of course, but it’s weird.

// AppDelegate.m
 if(isAutoLogin) {self.tabBarCtl = [[HSUTabBarController alloc] init]; self.window.rootViewController = self.tabBarCtl; [self.tabBarCtl autoLogin]; } // hsutabBarController.m implement API proxy method - (void)loginSuccess {// there is no need to set automatic loginSuccess} - (void) loginFailure {// // AlertCtl display button program root controller set back to login controller}Copy the code

conclusion

  • The first essentially calls the login API every time, while the second formal approach is to determine what request to make based on the token.

2018.7.25 update

Next, look at tokens. Because we need to use background data, no demo demo.

Token is a login token used to determine the login status of the current user!

Draw a graph to illustrate the implementation of tokens.

When the user logs in from device A, the server uses an algorithm to generate A token, say 1 (actually A long string), saves it in the database and returns the token to device A. A receives the token, records it, and carries it in all network requests that require it. If the user logs in from device B, the server generates a new token (hypothesis 2), and (in the case of multiple device logins not supported) the old token “1” is discarded. If A then initiates A request and the server validates the token, it may tell A “this account is logged in elsewhere”.

Learn more about tokens.

  • Token is time-sensitive. The timeliness is set by the background according to specific needs. Like chat apps, the timeliness can last up to a year. Like payment apps, timeliness should be shorter. After the token expires, the server receives a request and finds that the token is invalid. The server tells the initiator that the token has not been operated for a long time. Please log in again.
  • Token can not be unique at the same time. Considering this situation, a video member account supports at most three login ends. A, B, and C can watch the video at the same time. If the three tokens are still valid, D may not be able to log in, or an online device may be blocked. The database state should hold three valid tokens. QQ this kind of mobile phone end, computer end principle is similar.
  • If the token status changes, the original token fails to be logged in to elsewhere, the account password is changed, or the password expires. In this case, users should be reminded to clear the saved token and return to the login page.

Using a token

Step 1 The token is saved and obtained

Compare the following two methods. In terms of security, both can be exported directly from unjailbroken phones. In terms of convenience, UserDefault is simpler. The good thing about cookies is that they can set the expiration time and determine whether the identity information is expired.

  • Method one is easiest to save in NSUserDefault
/ / save [userDefaultssetObject:token forKey:@"token"]; [userDefaults synchronize]; // get [userDefaults objectForKey:@]"token"]; // delete [userDefaults removeObjectForKey:@"token"];
Copy the code
  • Method 2: Use cookies Cookies – data stored on the user’s local terminal. In fact, the dictionary generates the cookie file and saves it in the App package. If the App is connected to other WebViews, the automatic login method is also used.

Apple has already wrapped it for us, using two classes. NSHTTPCookie: the object in which NSHTTPCookie is stored. Cookies must be set to expire (locally), otherwise the App will be cleared when closed.

It is recommended to encapsulate a utility class. HSUCookieTool.h

#import <Foundation/Foundation.h>@interface HSUCookieTool : NSObject /** Generates the cookie @param name The name of the cookie @param value The value of the cookie @param domain Domain name */ + (void)saveCookieWithName:(NSString *)name value:(NSString *)value domain:(NSString *)domain; /** delete cookie @param name cookie name */ + (void)deleteCookieWithName:(NSString *)name; /** get cookie @param name Cookie name @returnCookie, which may be empty */ + (NSHTTPCookie *)cookieWithName:(NSString *)name; @endCopy the code

HSUCookieTool.m

#import "HSUCookieTool.h"@implementation HSUCookieTool + (void)saveCookieWithName:(NSString *)name value:(NSString *)value domain:(NSString // Save NSMutableDictionary *cookieProperties = [NSMutableDictionary dictionary]; // Name the cookie [cookieProperties]setObject:name  forKey:NSHTTPCookieName]; // Set the value [cookieProperties]setObject:value forKey:NSHTTPCookieValue]; // The directory is usually @"/"
    [cookieProperties setObject:@"/" forKey:NSHTTPCookiePath]; // Set the local expiration time after one year // Close App without setting [cookieProperties]setValue:[NSDate dateWithTimeIntervalSinceNow:3600*24*30*12] forKey:NSHTTPCookieExpires]; // Set the domain name [cookieProperties]setObject:[NSURL URLWithString:domain].host forKey:NSHTTPCookieDomain]; / / generated the 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]) {
            returncookie; }}return nil;
}


@end
Copy the code

Note that saving the same cookie in the same path will replace the previous cookie with the same name. However, it is recommended to delete the token before adding it.

Step 2 The request contains a token

The API proxy method should be able to set request headers in each API

- (AFHTTPRequestSerializer < AFURLRequestSerialization > *) requestSerializer {/ / to get local token NSHTTPCookie * cookie = [HSUCookieTool cookieWithName:@"token"]; NSString *token = cookie.value; // assume that the background specifies @"AuthorisedToken"
  AFHTTPRequestSerializer *requestSer = [AFHTTPRequestSerializer serializer];
    [requestSer setValue:token forHTTPHeaderField:@"AuthorisedToken"];
  return requestSer;
}

Copy the code

Finally, local information encryption

After studying the token, it is found that local users can automatically log in without remembering the password. Same thing with WX. The interface can be implemented by account and token, there is no password, after all, not saving the password locally is safer than any encryption. However, if no token is implemented in the background and automatic login is required, the login interface can only be invoked each time. This inevitably involves a password.

We talked about NSUserDefaults and NSHTTP cookies. If you don’t jailbreak your phone, you can export information directly, which makes you feel insecure. So there is a need for encryption.

  • Method 1 Saving passwords through Keychain The iOS Keychain service provides a way to securely store private information (such as passwords, serial numbers, and certificates). Each iOS program has an independent Keychain. But it doesn’t feel good to delete the App. Essentially, it’s a safe place to keep your password. It is very easy to use, and there are many packaged tool libraries. I’m not going to expand it here.

  • Method 2 Use an algorithm to encrypt data. Apple provides many encryption algorithms. This section describes Base64 encryption.

Use nsstrings + encrypt implementation

// Given a string, Base64 encodes the string, Then return the encoded result - (NSString *)base64EncodeString {// First convert the string to binary data NSData *data = [self dataUsingEncoding:NSUTF8StringEncoding]; // Base64 encodes binary data and returns the encoded stringreturn[data base64EncodedStringWithOptions:0]; } // Base64 decodeString - (NSString *)base64DecodeString {//1. The base64 encoded string "decode" for binary data NSData * data = [[NSData alloc] initWithBase64EncodedString: self options: 0]; //2. Convert binary data to string returnsreturn [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding];
}
Copy the code

Safer practice

Specify an encryption algorithm together with the background to encrypt the password parameters of the network request. Decrypt it when you get to the server. In this way, even if the packet capture password can not be directly exposed. This needs to be negotiated with the backstage.


conclusion

  • 1. Determine whether to load the main function interface or the login interface in the AppDelegate. 2. Set automatic login after successful login, encrypt the password and token using Base64 encryption method, and save the password and token. 3. Handle business logic wherever it matters.

  • Business Logic 1 ️ store account 2 ️ whether to remember password 3 discount store password 4 discount on automatic login 5 discount store token

    Successful login must 1 ️5 ️. If the option (remembering password and automatic login), 2 ️, 3 discount ️ and 4 discount. The discount or cancellation of account needs ❌4 discount ️, ❌5 discount. Incorrect password (suddenly changed), ❌2 ️, ❌3 one, discount 4 one, discount 5 one. Token expiration, ❌4 discount ️, ❌5 discount. Remember to change local tokens where there are token updates.

Place of doubt

  • Login window. By changing the rootViewController to switch controller and main function controller is wrong? The project I was working on was presented. But the online references are.
  • Is it a bit of a storm in a teacup to save your token with a cookie? The advantage over NSUserDefaults is that there’s only one local expiration property.