迅速小吃106:免受伤害
在105中,我们使用NSUserDefaults记录“会话状态”。 这种方法是有缺陷的,因为它不能为入侵式黑客提供足够的保护。
在这次冒险中,我们将努力使用Apple的Keychain技术来升级我们的应用程序的防御。
首先,让我们向RegistrationLoginController添加两个新的TextField(即:emailTextField和passwordTextField)和一个SignIn按钮。
注意,我们已经定义了一个TextFieldTag枚举(枚举)。 定位单个文本字段时,这将派上用场。 根据标准惯例,我们接着将元素添加为子视图,并将其放置在视图控制器的视图上。 最后,我们指定在选择新按钮signIn()时应调用的方法。
向RegistrationLoginController添加扩展,以访问与编辑和验证输入到我们的textfield对象中的文本有关的可选方法。
实现“ textFieldShouldReturn”以在按下键盘的返回按钮时插入指令。 Guard语句(// 1)检查所关注的文本字段是否为空。 如果是这样,则该函数返回false并在遍历switch语句之前终止。 否则,如果emailTextField处于焦点位置,按回车键会将视图焦点更改为passwordTextField。 或者,如果passwordTextField是第一响应者,则switch语句将执行signIn()函数。
signIn()函数将充当我们嵌入使用Apple的Keychain技术收集的敏感信息的第一步。 我们将使用此功能来收集我们打算加密的所有数据。 为了帮助完成此任务,让我们构建一个自定义的“用户”数据类型(也称为struct)以提供其相关属性的存储:名称和电子邮件。
首先注释掉105的UserDefaults(// 1)的实现。 关闭键盘(// 2)。 确保电子邮件和密码文本字段均包含文本(// 3),如果是这样,则捕获电子邮件和密码用户输入,否则返回。 接下来(在// 4中),将值分配给’name’和’user’常量声明。 在将“名称”和“电子邮件”(输入到我们的电子邮件textField中的文本)添加到我们的自定义数据类型的实例中之前,将当前设备名称添加到“名称”中。
苹果的钥匙串是用于存储敏感信息的专用数据库。 它是iOS开发人员可以使用的最重要的安全元素之一。 让我们用它来存储关键用户数据。 与框架的直接交互非常复杂,因为它主要是用C编写的。幸运的是,我们可以使用“ KeychainPaswwordItem”(来自Apple示例代码(GenericKeychain)的Swift包装层),因为它充当了易于使用的界面接口。钥匙串框架。
为了补充钥匙串,让我们通过cocoapods添加“ CryptoSwift”。 如果攻击者要破坏苹果的钥匙串,她可以在纯文本文件中读取敏感的用户信息。 CryptoSwift将用于存储根据用户的敏感数据构造的“哈希”。
创建AuthController.swift,这是一个静态类,用于控制应用程序的身份验证。 苹果公司的KeychainPasswordItem允许访问其“ savePassword”方法,该方法将密码字符串安全地存储在钥匙串中。 创建带有定义的“服务名称”常量声明以及唯一标识符(即用户的电子邮件)的KeychainPasswordItem。
通过设置“ Settings.currentUser”,继续将用户存储在UserDefaults中。
在此阶段,我们已将用户密码(以明文形式)存储在“钥匙串”中。 为了与最佳实践保持一致,让我们使用CryptoSwift将密码存储为难以理解的文本。
“ passwordHash”接受电子邮件和密码。 在函数内部,我们添加了“盐”常量声明,这是一个随机数据字符串,用作单向哈希函数的附加输入。 Salts可以防御字典攻击,从而使系统遍历一大堆密码,目的是跨过应用程序中使用的密码。 添加盐会极大地增强对字典攻击的防御能力,因为入侵者除了应用程序密码外还必须正确猜测所应用的盐。 通过将SHA-2哈希应用于通过连接密码,电子邮件和盐字符串构建的字符串,可以完成该功能。 修改类func signIn,以将散列的字符串输入KeychainPasswordItem的’savePassword’方法。
返回RegistrationLoginController并完成其signIn()方法,以调用AuthController的authControllerSignIn函数。 注释掉最后一行,该行以前用于切换到我们的应用程序MainScreen导航堆栈。
现在,我们已经保存了用户和哈希密码,是时候使用此数据来管理状态适当的UINavigation堆栈的表示了。 在AuthController.swift中,我们将提供一种方法来确定应用程序用户是否为“ signedIn”。
如果未设置当前用户,则isSignedIn方法将返回“ false”。 否则,如果存在密码,从而满足其文本包含大于零个字符的条件,则该方法返回true。
在AuthController.swift的底部,使用Notification.Name扩展名为“ loginStatusChanged”定义静态常量声明。
从Swifty Snack 105中,我们知道RootController类的切换决定了向用户呈现哪个UINavigation堆栈。 现在,我们将编辑它的init()方法,以将其作为观察者分配给“ .loginStatusChanged” NavigationCenter调用。 因此,当应用程序的“ .loginStatusChanged”启动时,RootController将执行其changedStatus方法。 如果当前用户已登录,则应用程序将从RegistrationLoginController切换到MainController。 或者,该应用将继续显示或还原到RegistrationLoginController。
返回AuthController.swift为用户退出我们的应用程序构建路径。 添加signOut()方法。
在这里,我们检查是否设置了currentUser。 如果没有,我们退出该方法,因为用户无需注销。 否则,如果用户已登录,我们将从“钥匙串”中删除其密码哈希,然后将currentUser重置为nil。 最后,我们将.loginStatusChanged调用发送给响应控制器。
在我们的MainController.swift内部,更新logout()函数以调用AuthController的logout()函数。
在显示初始SplashController之后,我们的应用程序现在配置为根据用户的登录状态显示RegistrationLoginController或其MainController。
“幸福的最重要秘诀是认识到幸福是您做出的选择和发展的技能。 您选择快乐,然后努力工作。 就像在锻炼肌肉。” —海军拉维坎特