1、Sign up/in

1.1 用户登录安全原则

  • 不能在网络上传输用户隐私数据的明文。
  • 不能在本地和服务器上存储用户隐私数据的明文。

1.2 用户登录流程

  • 登录成功之后,应该跳转视图控制器到主页。

  • 如果用户上次登录成功,启动应用程序时,直接进入主页。

  • 当用户主动注销的时候,返回登录页面。

  • 在实际开发中,关于网络方面的代码执行,通常会有一个单例统一管理。涉及到网络就涉及到多线程的异步,需要控制最大并发数。

1.3 iOS 中加解密

2、明文登录

  • Objective-C

    • GET 登录

      	// 用户名
      NSString *username = self.usernameText.text; // 用户密码明文
      NSString *pwd = self.pwdText.text; NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:
      @"http://192.168.88.200/login/login.php?username=%@&password=%@", username, pwd]];
      NSURLRequest *request = [NSURLRequest requestWithURL:url]; [[[NSURLSession sharedSession] dataTaskWithRequest:request
      completionHandler:^(NSData * _Nullable data,
      NSURLResponse * _Nullable response,
      NSError * _Nullable error) { if (error == nil || data != nil) {
      NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:data options:0 error:NULL];
      if ([dict[@"userId"] intValue] > 0) {
      [self saveUserLoginInfo];
      }
      }
      }] resume];
    • POST 登录

      	// 用户名
      NSString *username = self.usernameText.text; // 用户密码明文
      NSString *pwd = self.pwdText.text; NSURL *url = [NSURL URLWithString:@"http://192.168.88.200/login/login.php"];
      NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; request.HTTPMethod = @"POST";
      request.HTTPBody = [[NSString stringWithFormat:@"username=%@&password=%@", username, pwd]
      dataUsingEncoding:NSUTF8StringEncoding]; [[[NSURLSession sharedSession] dataTaskWithRequest:request
      completionHandler:^(NSData * _Nullable data,
      NSURLResponse * _Nullable response,
      NSError * _Nullable error) { if (error == nil || data != nil) {
      NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:data options:0 error:NULL];
      if ([dict[@"userId"] intValue] > 0) {
      [self saveUserLoginInfo];
      }
      }
      }] resume];

3、Base64 编码登录

  • Base64 编码具体实现代码见 GitHub 源码 QExtension

  • Objective-C

    • NSString+Base64.m

      	// 对 ASCII 编码的字符串进行 base64 编码
      - (NSString *)base64Encode { NSData *data = [self dataUsingEncoding:NSUTF8StringEncoding];
      return [data base64EncodedStringWithOptions:0];
      } // 对 base64 编码的字符串进行解码
      - (NSString *)base64Decode { NSData *data = [[NSData alloc] initWithBase64EncodedString:self options:0];
      return [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
      }
    • POST 登录

      	// 用户名
      NSString *username = self.usernameText.text; // 经 base64 编码的用户密码
      NSString *pwd = [self.pwdText.text base64Encode]; NSURL *url = [NSURL URLWithString:@"http://192.168.88.200/login/login.php"];
      NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; request.HTTPMethod = @"POST";
      request.HTTPBody = [[NSString stringWithFormat:@"username=%@&password=%@", username, pwd]
      dataUsingEncoding:NSUTF8StringEncoding]; [[[NSURLSession sharedSession] dataTaskWithRequest:request
      completionHandler:^(NSData * _Nullable data,
      NSURLResponse * _Nullable response,
      NSError * _Nullable error) { if (error == nil || data != nil) {
      NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:data options:0 error:NULL];
      if ([dict[@"userId"] intValue] > 0) {
      [self saveUserLoginInfo];
      }
      }
      }] resume];

4、MD5 加密登录

  • MD5 加密具体实现代码见 GitHub 源码 QExtension

  • Objective-C

    • NSString+Hash.m

      	#import <CommonCrypto/CommonCrypto.h>
      
      	// MD5 散列函数
      - (NSString *)md5String { const char *str = self.UTF8String;
      uint8_t buffer[CC_MD5_DIGEST_LENGTH]; CC_MD5(str, (CC_LONG)strlen(str), buffer); return [self stringFromBytes:buffer length:CC_MD5_DIGEST_LENGTH];
      } // HMAC 散列函数
      - (NSString *)hmacMD5StringWithKey:(NSString *)key { const char *keyData = key.UTF8String;
      const char *strData = self.UTF8String;
      uint8_t buffer[CC_MD5_DIGEST_LENGTH]; CCHmac(kCCHmacAlgMD5, keyData, strlen(keyData), strData, strlen(strData), buffer); return [self stringFromBytes:buffer length:CC_MD5_DIGEST_LENGTH];
      } // 时间戳散列函数
      - (NSString *)timeMD5StringWithKey:(NSString *)key { NSString *hmacKey = key.md5String;
      NSString *hmacStr = [self hmacMD5StringWithKey:hmacKey]; NSDateFormatter *fmt = [[NSDateFormatter alloc] init];
      fmt.dateFormat = @"yyyy-MM-ddHH:mm";
      NSString *dateStr = [fmt stringFromDate:[NSDate date]]; hmacStr = [hmacStr stringByAppendingString:dateStr]; return [hmacStr hmacMD5StringWithKey:hmacKey];
      } // 助手方法
      - (NSString *)stringFromBytes:(uint8_t *)bytes length:(int)length { NSMutableString *strM = [NSMutableString string]; for (int i = 0; i < length; i++) {
      [strM appendFormat:@"%02x", bytes[i]];
      } return [strM copy];
      }
    • 直接 md5 加密

      	// 用户名
      NSString *username = self.usernameText.text; // 经 md5 加密的用户密码
      NSString *pwd = [self.pwdText.text md5String]; NSURL *url = [NSURL URLWithString:@"http://192.168.88.200/login/login.php"];
    • md5 + 盐 加密

      	// “盐”,随机添加的字符串,要够长,够复杂
      static NSString *salt = @"FUYGIUHIJKJHVBNOIUYUGVHJK@#$%^&))(&*^%W%$^%ukjsbcjcgvbk,jmhnrhjbjknklHDYCHGNKJB"; // 用户名
      NSString *username = self.usernameText.text; // 经 md5 加 盐 加密的用户密码
      NSString *pwd = [[self.pwdText.text stringByAppendingString:salt] md5String]; NSURL *url = [NSURL URLWithString:@"http://192.168.88.200/login/login.php"];
    • md5 + HMAC 加密

      	// 用户名
      NSString *username = self.usernameText.text; // 经 md5 加 HMAC 加密的用户密码
      NSString *pwd = [[self.pwdText.text hmacMD5StringWithKey:@"qianqian"] md5String]; NSURL *url = [NSURL URLWithString:@"http://192.168.88.200/login/login.php"];
    • md5 + HMAC + 时间戳 加密

      	// 用户名
      NSString *username = self.usernameText.text; // 经 md5 加 HMAC 加 时间戳 加密的用户密码
      NSString *pwd = [self.pwdText.text timeMD5StringWithKey:@"qianqian"]; NSURL *url = [NSURL URLWithString:@"http://192.168.88.200/login/loginhmac.php"];
    • 登录代码段

      	NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
      
      	request.HTTPMethod = @"POST";
      request.HTTPBody = [[NSString stringWithFormat:@"username=%@&password=%@", username, pwd]
      dataUsingEncoding:NSUTF8StringEncoding]; [[[NSURLSession sharedSession] dataTaskWithRequest:request
      completionHandler:^(NSData * _Nullable data,
      NSURLResponse * _Nullable response,
      NSError * _Nullable error) { if (error == nil || data != nil) {
      NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:data options:0 error:NULL];
      if ([dict[@"userId"] intValue] > 0) {
      [self saveUserLoginInfo];
      }
      }
      }] resume];

5、完整登录

  • Objective-C

    • AppDelegate.m

      	- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
      
          	// 注册通知
      /*
      AppDelegate 中可以不移除
      */ [[NSNotificationCenter defaultCenter] addObserver:self
      selector:@selector(loginSuccess)
      name:HQUserLoginSuccessedNotification
      object:nil];
      [[NSNotificationCenter defaultCenter] addObserver:self
      selector:@selector(logout)
      name:HQUserLogoutNotification
      object:nil];
      [[NSNotificationCenter defaultCenter] addObserver:self
      selector:@selector(touchIDAuthentication)
      name:HQTouchIDAuthenticationNotification
      object:nil]; // 直接用户登录 // 如果成功,进入主页
      [[NetworkTools sharedNetworkTools] userLoginWithTouchID:(BOOL)YES Failed:^{ // 如果失败,进入登录页面
      [self logout];
      }]; return YES;
      } /// 登录成功
      - (void)loginSuccess { // 显示主页面
      [self switchStroyboard:@"Home"];
      } /// 用户注销
      - (void)logout { // 显示登录页面
      [self switchStroyboard:@"Login"];
      } /// 指纹验证
      - (void)touchIDAuthentication {
      [self switchStroyboard:@"TouchID"];
      } /// 切换显示页面
      - (void)switchStroyboard:(NSString *)sbName { UIStoryboard *sb = [UIStoryboard storyboardWithName:sbName bundle:nil];
      self.window.rootViewController = sb.instantiateInitialViewController;
      }
    • HomeViewController.m

      	#import "NetworkTools.h"
      
      	/// 用户注销
      - (IBAction)logout:(UIButton *)sender { [[NetworkTools sharedNetworkTools] userLogout];
      }
    • LoginViewController.m

      	#import "NetworkTools.h"
      
      	@property (weak, nonatomic) IBOutlet UITextField *usernameText;
      @property (weak, nonatomic) IBOutlet UITextField *pwdText;
      @property (weak, nonatomic) IBOutlet UISwitch *savePwdSwitch; - (void)viewDidLoad {
      [super viewDidLoad]; [self loadUserInfo];
      } /// 用户登录 - (IBAction)postLogin { NetworkTools *tools = [NetworkTools sharedNetworkTools]; tools.username = self.usernameText.text;
      tools.pwd = self.pwdText.text;
      tools.isSavePwd = self.savePwdSwitch.isOn; [tools userLoginWithTouchID:(BOOL)NO Failed:^{
      UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"提示"
      message:@"用户名或密码错误"
      preferredStyle:UIAlertControllerStyleAlert];
      [alertController addAction:[UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleCancel handler:nil]];
      [self presentViewController:alertController animated:YES completion:nil];
      }];
      } /// 加载用户信息 - (void)loadUserInfo { self.usernameText.text = [NetworkTools sharedNetworkTools].username;
      self.pwdText.text = [NetworkTools sharedNetworkTools].pwd;
      } /// 键盘回收 - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
      [self.view endEditing:YES];
      }
    • TouchIDViewController.m

      	#import "NetworkTools.h"
      
      	- (void)viewDidLoad {
      [super viewDidLoad]; [self touchIDAuthentication];
      } - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { [self touchIDAuthentication];
      } /// 指纹验证 - (void)touchIDAuthentication { NetworkTools *tools = [NetworkTools sharedNetworkTools]; [tools touchIDAuthenticationFailed:^{
      UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"提示"
      message:@"指纹验证失败,请重试"
      preferredStyle:UIAlertControllerStyleAlert];
      [alertController addAction:[UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleCancel handler:nil]];
      [self presentViewController:alertController animated:YES completion:nil];
      }];
      }
    • NetworkTools.h

      	extern NSString * const HQUserLoginSuccessedNotification;
      extern NSString * const HQUserLogoutNotification;
      extern NSString * const HQTouchIDAuthenticationNotification; @interface NetworkTools : NSObject /// 用户名
      @property (nonatomic, copy) NSString *username; /// 用户口令
      @property (nonatomic, copy) NSString *pwd; /// 是否记住密码
      @property (nonatomic, assign) BOOL isSavePwd; /// 单例方法
      + (instancetype)sharedNetworkTools; /// 用户登录
      - (void)userLoginWithTouchID:(BOOL)touch Failed:(void (^)())failed; /// 用户注销
      - (void)userLogout; /// 用户指纹验证
      - (void)touchIDAuthenticationFailed:(void (^)())failed; @end
    • NetworkTools.m

      	#import "NSString+TimePwd.h"
      #import "SSKeychain.h"
      #import <LocalAuthentication/LocalAuthentication.h> // 定义通知字符串常量
      NSString * const HQUserLoginSuccessedNotification = @"HMUserLoginSuccessedNotification";
      NSString * const HQUserLogoutNotification = @"HMUserLogoutNotification";
      NSString * const HQTouchIDAuthenticationNotification = @"HQTouchIDAuthenticationNotification"; @implementation NetworkTools /**
      单例创建中,使用 allocWithZone, copyWithZone ... 等等方法,会把所有创建第二个实例可能性全部堵死。
      在真正开发中,有的时候,会需要额外创建一个副本。
      */
      + (instancetype)sharedNetworkTools { static id instance; static dispatch_once_t onceToken;
      dispatch_once(&onceToken, ^{
      instance = [[self alloc] init];
      });
      return instance;
      } - (instancetype)init {
      self = [super init];
      if (self) { // 第一次被实例化的时候,加载用户信息
      [self loadUserLoginInfo];
      }
      return self;
      } /// 用户登录 /**
      程序启动直接执行,如果登录成功,进入主界面。如果登录失败,进入登录页面。
      */
      - (void)userLoginWithTouchID:(BOOL)touch Failed:(void (^)())failed { NSString *username = self.username; // md5 + HMAC + 时间戳 加密
      NSString *pwd = [self.pwd timeMD5StringWithKey:@"qianqian"]; NSURL *url = [NSURL URLWithString:@"http://192.168.88.200/login/loginhmac.php"];
      NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; request.HTTPMethod = @"POST";
      request.HTTPBody = [[NSString stringWithFormat:@"username=%@&password=%@", username, pwd]
      dataUsingEncoding:NSUTF8StringEncoding]; [[[NSURLSession sharedSession] dataTaskWithRequest:request
      completionHandler:^(NSData * _Nullable data,
      NSURLResponse * _Nullable response,
      NSError * _Nullable error) { if (error == nil || data != nil) { NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:data options:0 error:NULL]; dispatch_async(dispatch_get_main_queue(), ^{ if ([dict[@"userId"] intValue] > 0) { [self saveUserLoginInfo]; if (touch == NO || [UIDevice currentDevice].systemVersion.floatValue < 8.0) { // 发送登录成功通知
      [[NSNotificationCenter defaultCenter] postNotificationName:HQUserLoginSuccessedNotification object:nil];
      } else { // 发送指纹验证通知
      [[NSNotificationCenter defaultCenter] postNotificationName:HQTouchIDAuthenticationNotification object:nil];
      }
      } else {
      if (failed != nil) { // 执行失败回调
      failed();
      }
      }
      });
      }
      }] resume];
      } /// 用户注销 - (void)userLogout { [SSKeychain deletePasswordForService:[NSBundle mainBundle].bundleIdentifier account:self.username]; // 发送注销通知
      [[NSNotificationCenter defaultCenter] postNotificationName:HQUserLogoutNotification object:nil];
      } /// 指纹验证 - (void)touchIDAuthenticationFailed:(void (^)())failed { // 实例化本地身份验证上下文
      LAContext *context= [[LAContext alloc] init]; // 判断是否支持指纹识别
      if (![context canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics error:NULL]) { // 发送登录成功通知
      [[NSNotificationCenter defaultCenter] postNotificationName:HQUserLoginSuccessedNotification object:nil]; return;
      } [context evaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics
      localizedReason:@"请验证已有指纹"
      reply:^(BOOL success, NSError * _Nullable error) { dispatch_async(dispatch_get_main_queue(), ^{ // 输入指纹开始验证,异步执行
      if (success) { // 发送登录成功通知
      [[NSNotificationCenter defaultCenter] postNotificationName:HQUserLoginSuccessedNotification object:nil];
      } else {
      switch (error.code) {
      case -1:
      if (failed != nil) { // 执行失败回调
      failed();
      }
      break; case -2:
      case -3:
      case -4:
      case -5:
      case -6:
      case -7:
      // 发送注销通知
      [[NSNotificationCenter defaultCenter] postNotificationName:HQUserLogoutNotification object:nil];
      break; default:
      break;
      }
      }
      });
      }];
      } /// 用户登录信息存储 - (void)saveUserLoginInfo { NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults]; [userDefaults setObject:self.username forKey:@"userNameKey"];
      [userDefaults setBool:self.isSavePwd forKey:@"isSavePwdKey"];
      [userDefaults synchronize]; if (self.isSavePwd) { // 将密码保存到钥匙串中
      [SSKeychain setPassword:self.pwd forService:[NSBundle mainBundle].bundleIdentifier account:self.username];
      } else {
      self.pwd = nil;
      [SSKeychain deletePasswordForService:[NSBundle mainBundle].bundleIdentifier account:self.username];
      }
      } - (void)loadUserLoginInfo { NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
      self.username = [userDefaults objectForKey:@"userNameKey"];
      self.isSavePwd = [userDefaults boolForKey:@"isSavePwdKey"]; // 将密码从钥匙串中取出
      self.pwd = [SSKeychain passwordForService:[NSBundle mainBundle].bundleIdentifier account:self.username];
      } @end
    • NSString+Hash.m

      	#import <CommonCrypto/CommonCrypto.h>
      
      	/// 时间戳散列函数
      
      	- (NSString *)timeMD5StringWithKey:(NSString *)key {
      NSString *hmacKey = key.md5String;
      NSString *hmacStr = [self hmacMD5StringWithKey:hmacKey]; NSDateFormatter *fmt = [[NSDateFormatter alloc] init];
      fmt.dateFormat = @"yyyy-MM-ddHH:mm";
      NSString *dateStr = [fmt stringFromDate:[NSDate date]]; hmacStr = [hmacStr stringByAppendingString:dateStr]; return [hmacStr hmacMD5StringWithKey:hmacKey];
      } /// HMAC 散列函数 - (NSString *)hmacMD5StringWithKey:(NSString *)key {
      const char *keyData = key.UTF8String;
      const char *strData = self.UTF8String;
      uint8_t buffer[CC_MD5_DIGEST_LENGTH]; CCHmac(kCCHmacAlgMD5, keyData, strlen(keyData), strData, strlen(strData), buffer); return [self stringFromBytes:buffer length:CC_MD5_DIGEST_LENGTH];
      } /// 散列函数 - (NSString *)md5String {
      const char *str = self.UTF8String;
      uint8_t buffer[CC_MD5_DIGEST_LENGTH]; CC_MD5(str, (CC_LONG)strlen(str), buffer); return [self stringFromBytes:buffer length:CC_MD5_DIGEST_LENGTH];
      } /// 助手方法 - (NSString *)stringFromBytes:(uint8_t *)bytes length:(int)length {
      NSMutableString *strM = [NSMutableString string]; for (int i = 0; i < length; i++) {
      [strM appendFormat:@"%02x", bytes[i]];
      } return [strM copy];
      }

6、保存用户信息

  • Objective-C

    • 明文保存

      	// 保存用户信息
      
      		NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];                 
      
      		[userDefaults setObject:self.usernameText.text forKey:@"userNameKey"];
      [userDefaults setObject:self.pwdText.text forKey:@"userPwdKey"]; [userDefaults synchronize]; // 加载用户信息 NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults]; self.usernameText.text = [userDefaults objectForKey:@"userNameKey"];
      self.pwdText.text = [userDefaults objectForKey:@"userPwdKey"];
    • base64 编码保存

      	// NSString+Base64.m
      
      		// 对 ASCII 编码的字符串进行 base64 编码
      - (NSString *)base64Encode { NSData *data = [self dataUsingEncoding:NSUTF8StringEncoding];
      return [data base64EncodedStringWithOptions:0];
      } // 对 base64 编码的字符串进行解码
      - (NSString *)base64Decode { NSData *data = [[NSData alloc] initWithBase64EncodedString:self options:0];
      return [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
      } // 保存用户信息 NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults]; [userDefaults setObject:self.usernameText.text forKey:@"userNameKey"]; // 对密码进行 base64 编码
      [userDefaults setObject:[self.pwdText.text base64Encode] forKey:@"userPwdKey"]; [userDefaults synchronize]; // 加载用户信息 NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults]; self.usernameText.text = [userDefaults objectForKey:@"userNameKey"]; // 对密码进行 base64 解码
      self.pwdText.text = [[userDefaults objectForKey:@"userPwdKey"] base64Decode];
    • 钥匙串保存

      	// 添加第三方库文件
      SSKeychain // 包含头文件
      #import "SSKeychain.h" // 保存用户信息 NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
      [userDefaults setObject:self.usernameText.text forKey:@"userNameKey"];
      [userDefaults synchronize]; // 将密码保存到钥匙串中
      [SSKeychain setPassword:self.pwdText.text
      forService:[NSBundle mainBundle].bundleIdentifier
      account:self.usernameText.text]; // 加载用户信息 NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
      self.usernameText.text = [userDefaults objectForKey:@"userNameKey"]; // 将密码从钥匙串中取出
      self.pwdText.text = [SSKeychain passwordForService:[NSBundle mainBundle].bundleIdentifier
      account:self.usernameText.text];

7、SSKeychain/SAMKeychain 的使用

  • GitHub 网址:https://github.com/soffes/SAMKeychain

  • SSKeychain/SAMKeychain 使用 ARC

  • Objective-C

    	// 添加第三方库文件
    SSKeychain // 包含头文件
    #import "SSKeychain.h" // 获取所有的账户信息 // 只有同一个开发者开发的应用程序,才能够互相看到账号
    NSArray *allAccounts = [SSKeychain allAccounts]; // 获取指定服务名的账户信息 NSArray *accounts = [SSKeychain accountsForService:[NSBundle mainBundle].bundleIdentifier]; // 将密码保存到钥匙串中
    /*
    + (BOOL)setPassword:(NSString *)password forService:(NSString *)serviceName account:(NSString *)account; 参数:
    password :密码明文
    serviceName:服务名,可以随便写,建议使用 bundleId,应用程序的唯一标示,每一个上架的应用程序,都有一个唯一的 bundleId
    account :账户名(用户名)
    */ // 保存 NSString 格式的密码
    [SSKeychain setPassword:self.pwdText.text
    forService:[NSBundle mainBundle].bundleIdentifier
    account:self.usernameText.text]; NSData *pwdData = [self.pwdText.text dataUsingEncoding:NSUTF8StringEncoding]; // 保存 NSData 格式的密码
    [SSKeychain setPasswordData:pwdData
    forService:[NSBundle mainBundle].bundleIdentifier
    account:self.usernameText.text]; // 将密码从钥匙串中取出 // 获取 NSString 格式的密码
    self.pwdText.text = [SSKeychain passwordForService:[NSBundle mainBundle].bundleIdentifier
    account:self.usernameText.text]; // 获取 NSData 格式的密码
    NSData *pwdData1 = [SSKeychain passwordDataForService:[NSBundle mainBundle].bundleIdentifier
    account:self.usernameText.text]; // 将密码从钥匙串中删除 [SSKeychain deletePasswordForService:[NSBundle mainBundle].bundleIdentifier
    account:self.usernameText.text];

iOS - Sign up/in 注册/登录的更多相关文章

  1. vue2.0+koa2+mongodb实现注册登录

    前言 前段时间和公司一个由技术转产品的同事探讨他的职业道路,对我说了一句深以为然的话: "不要把自己禁锢在某一个领域,技术到产品的转变,首先就是思维上的转变.你一直做前端,数据的交互你只知道 ...

  2. 连接数据库+注册->登录->抽奖(有关联关系的接口)

    注册账号信息需要写入数据库,登录和抽奖时从数据库获取数据 一.连接数据库 my_sql.py: import pymysql class MyDb: def __init__(self,host,pa ...

  3. 巨蟒django之CRM1 需求分析&&表结构设计&&注册登录验证

    1.需求分析 .项目 ()业务 ()权限的管理 .CRM customer relationship management 客户关系管理系统 .谁来使用CRM? 销售&&班主任& ...

  4. 很实用的HTML5+CSS3注册登录窗体切换效果

    1. [代码]3个很实用的HTML5+CSS3注册登录窗体切换效果 <!DOCTYPE html><!--[if lt IE 7 ]> <html lang=" ...

  5. 一步步开发自己的博客 .NET版(3、注册登录功能)

    前言 这次开发的博客主要功能或特点:    第一:可以兼容各终端,特别是手机端.    第二:到时会用到大量html5,炫啊.    第三:导入博客园的精华文章,并做分类.(不要封我)    第四:做 ...

  6. Android开发案例 - 注册登录

    本文只涉及UI方面的内容, 如果您是希望了解非UI方面的访客, 请跳过此文. 在微博, 微信等App的注册登录过程中有这样的交互场景(如下图): 打开登录界面 在登录界面中, 点击注册, 跳转到注册界 ...

  7. iOS开发之记录用户登录状态

    iOS开发之记录用户登录状态 我们知道:CoreData的配置和使用步骤还是挺复杂的.但熟悉CoreData的使用流程后,CoreData还是蛮好用的.今天要说的是如何记录我们用户的登陆状态.例如微信 ...

  8. 如何设计一个 App 的注册登录流程?

    移 动设备发力之前的登录方式很简单:用户名/邮箱+密码+确认密码,所有的用户登录注册都是围绕着邮箱来做.随着移动设备和社交网络的普及,邮箱不再是唯 一,渐渐的出现了微博,QQ,微信等第三方登录方式,手 ...

  9. Discuz! X2.5判断会员登录状态及外部调用注册登录框

    Discuz! X2.5判断会员登录状态及外部调用注册登录框 有关discuz论坛会员信息,收集的一些资料: 用dedecms+discuz做了个门户加论坛形式的网站,但是dedecms顶部目前只能q ...

随机推荐

  1. C#:文件、文件夹特别操作

    1.过滤特殊字符 public class CharService:IDisposable { private List<char> _invalidChars; public CharS ...

  2. C#:将子Form加入父Form中

    实现的功能:已建立了多个子Form界面,在父Form界面左面,点击不同标题的链接文本,父Form界面右面显示不同的子界面内容. 具体如下: 1.加入split拆分器控件 2.在splitControl ...

  3. java用freemarker导出数据到word(含多图片)

    一.制作word模版 新建word文档,按照需要设置好字体等各种格式:这里为了显得整齐使用了无边框的表格. 将word文档另存为xml文件(注意不是word xml文档,我吃了这家伙的大亏了) 然后用 ...

  4. ACM题目————A simple problem

    Description Zty很痴迷数学问题..一天,yifenfei出了个数学题想难倒他,让他回答1 / n.但Zty却回答不了^_^. 请大家编程帮助他.   Input 第一行整数T,表示测试组 ...

  5. The shortest problem

    The shortest problem Time Limit: 3000/1500 MS (Java/Others) Memory Limit: 65536/65536 K (Java/Others ...

  6. hiho 第119周 最大权闭合子图

    描述 周末,小Hi和小Ho所在的班级决定举行一些班级建设活动. 根据周内的调查结果,小Hi和小Ho一共列出了N项不同的活动(编号1..N),第i项活动能够产生a[i]的活跃值. 班级一共有M名学生(编 ...

  7. 翻译之basename()

    NAME top basename, dirname - parse pathname components SYNOPSIS top #include <libgen.h> char * ...

  8. vilte/vowifi

    vendor/mediatek/proprietary/packages/services/Ims/src/com/mediatek/ims/ImsService.java ¦ ¦ ¦ ¦ ¦ ¦ v ...

  9. hbase centOS生产环境配置笔记 (1 NameNode, 1 ResourceManager, 3 DataNode)

    本次是第一次在生产环境部署HBase,本文若有配置上的不妥之处还请高手指正. hadoop版本:hadoop-2.4.1 HBase版本:hbase-0.98.6.1-hadoop2 Zookeepe ...

  10. IDEA打war包

    一 第一步创建一个web application:expolded 选择当前项目 二 新建一个web application: Archive 选择刚刚新建的Expoded  “For ‘...... ...