且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

如何使用fmdb登录页面?

更新时间:2023-12-01 16:05:10

如果要检查用户ID /密码组合是否在数据库中,您可以:

If you want to check to see if the userid/password combination is in your database, you can:

#import "FMDatabaseAdditions"

然后您可以看到用户ID /密码组合是否有资格成功登录:

and then you can see if the userid/password combination qualifies as a successful login like so:

BOOL success = NO;

NSInteger count = [database intForQuery:@"select count(*) from RegMembers where USERNAME = ? and PASSWORD = ?", username, password];

if (count > 0)
    success = YES;

话虽如此,我还有一些观察,从琐碎到关键:

Having said that, I have a couple of observations ranging from the trivial to the critical:


  1. 现在您总是报告已添加联系人。您应该确定插入是否成功。你可以通过检查 executeUpdate 方法返回的值来做到这一点(如果成功则返回 YES 如果 INSERT 失败)。

  1. Right now you're always reporting "contact added". You should really determine whether the insert succeeded or not. You can do this by checking the value returned by the executeUpdate method (returns YES if successful, NO if the INSERT failed).

使用 PRIMARY KEY AUTOINCREMENT 键,你不应该使用 DEFAULT 0 。首先,我不认为这会完成任何事情(无论如何,它将从 1 开始)。第二,即使它确实如此,使用零也是非常糟糕的,因为FMDB的 lastInsertRowId ,它调用SQLite sqlite3_last_insert_rowid ,使用零值表示数字键来指定错误。所以,永远不要使用零。

When using a PRIMARY KEY AUTOINCREMENT key, you should not use DEFAULT 0. First, I don't think that accomplishes anything (it will start at 1, regardless). Second, even if it did, it would be very bad to use zero because FMDB's lastInsertRowId, which calls SQLite sqlite3_last_insert_rowid, uses zero value for the numeric key to designate an error. So, never use zero.

更重要的是,你应该永远在未加密的设备上存储密码。 (即使您不关心应用程序的安全性,也有太多用户重新使用密码,因此您需要确保不会成为密码泄密的可能方式。)

Far more critical, you should never store passwords on the device unencrypted. (Even if you don't care about the security of your app, too many users re-use passwords, so you want to make sure that you don't become a possible way for their password to be compromised.)

至少,您希望在将密码存储在数据库中时加密密码,然后在登录时加密用户提供的密码,并比较两个加密密码。如果您想查看一些可用的加密服务,请参阅加密服务指南

At the very least, you'd want to encrypt the password when you store it in the database, and then, when logging in, encrypt the password that the user provided, and compare the two encrypted passwords. If you want to see some of the cryptographic services available, see the Cryptographic Services Guide.

话虽如此,您甚至可能不想自己编写任何密码加密代码(要做到这一点并不是完全无关紧要) 。

Having said that, you might not even want to write any password encryption code yourself (to do this properly is not entirely trivial).

使用iOS Keychain可能更好。 Apple关于该主题的入门读物是WWDC 2013视频使用钥匙串保护秘密

Using the iOS Keychain is probably better. Apple's primer on the topic is the WWDC 2013 video Protecting Secrets with the Keychain.

例如,要保存与用户ID相关联的密码,电子邮件地址和号码,您可以执行以下操作:

For example, to save the password, email address, and number associated with a userid, you could do something like:

// see if the userid already exists

NSDictionary* query = @{(__bridge id)kSecClass       : (__bridge id)kSecClassGenericPassword,
                        (__bridge id)kSecAttrService : @"com.domain.app",
                        (__bridge id)kSecAttrAccount : self.useridTextField.text,
                        (__bridge id)kSecMatchLimit  : (__bridge id)kSecMatchLimitOne,
                        (__bridge id)kSecReturnData  : @NO};

OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, NULL);

if (status == errSecSuccess)
{
    [[[UIAlertView alloc] initWithTitle:nil message:@"That userid is already registered" delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil] show];
    return;
}

// create payload of secret data

NSDictionary *secret = @{@"password" : self.passwordTextField.text ?: [NSNull null],
                         @"email"    : self.emailTextField.text    ?: [NSNull null],
                         @"number"   : self.numberTextField.text   ?: [NSNull null]};

NSData *data = [NSKeyedArchiver archivedDataWithRootObject:secret];

// create keychain query

query = @{(__bridge id)kSecClass       : (__bridge id)kSecClassGenericPassword,
          (__bridge id)kSecAttrService : @"com.domain.app",
          (__bridge id)kSecAttrAccount : self.useridTextField.text,
          (__bridge id)kSecValueData   : data};

// add data associated with the userid in the keychain

status = SecItemAdd((__bridge CFDictionaryRef)query, NULL);
NSAssert(status == errSecSuccess, @"%s: SecItemAdd error: %lu", __FUNCTION__, (unsigned long)status);

然后,当您想要登录时,您可以执行以下操作:

Then, when you want to log in, you can do something like:

BOOL success = NO;

NSDictionary* query = @{(__bridge id)kSecClass       : (__bridge id)kSecClassGenericPassword,
                        (__bridge id)kSecAttrService : @"com.domain.app",
                        (__bridge id)kSecAttrAccount : self.useridTextField.text,
                        (__bridge id)kSecMatchLimit  : (__bridge id)kSecMatchLimitOne,
                        (__bridge id)kSecReturnData  : @YES};

CFTypeRef typeRef;

OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &typeRef);

// if we found keychain entry for that userid, check the password

if (status == noErr)
{
    NSDictionary *dictionary = [NSKeyedUnarchiver unarchiveObjectWithData:(__bridge NSData *)typeRef];
    NSAssert(dictionary, @"%s: unarchiveObjectWithData failed: %@", __FUNCTION__, (__bridge NSData *)typeRef);
    NSString *password = dictionary[@"password"];
    if ([self.passwordTextField.text isEqualToString:password])
        success = YES;

    CFRelease(typeRef);
}

// if we didn't succeed for any reason, tell the user

if (!success)
{
    [[[UIAlertView alloc] initWithTitle:nil message:@"Login failed" delegate:nil cancelButtonTitle:nil otherButtonTitles:@"OK", nil] show];
}