文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

Best Practices for Building a Scalable iOS App with Use

2023-10-25 12:22

关注

作者:禅与计算机程序设计艺术

1.简介

随着移动应用的普及,越来越多的开发者开始关注到如何将其部署到App Store中发布,如何实现用户登录、注册等功能,并最终帮助App在市场上走向成功。然而在现实世界中,这其中存在很多难点需要解决,本文将会讨论构建可扩展、可靠的iOS应用程序时,如何安全、快速地进行用户验证以及授权。通过本文,读者能够了解到以下几点:

  1. 什么是Firebase?
  2. 用户身份认证(User Authentication)的必要性?
  3. 使用Firebase提供的认证服务,可以轻松地实现用户登录、注册功能吗?
  4. 为什么选择Firebase作为项目中的用户验证服务?
  5. 用户验证流程和原理?
  6. 提供安全保障的用户授权机制?
  7. 可扩展性和容错率如何影响用户验证性能?
  8. 设计好的用户验证系统的架构图?
  9. 当出现问题时该如何排查和处理?

2.基本概念术语说明

2.1 什么是Firebase?

Firebase是一个基于Google开发的一款云平台,提供了诸如数据存储、用户身份验证、消息推送等各种功能。它提供的所有服务都是免费的,而且目前已成为许多企业所采用的主要云服务之一。除此之外,还包括其Web SDK,可以用于前端开发者。

2.2 用户身份认证的必要性

随着互联网的发展,人们逐渐从线下活动转变成线上活动。传统的网站和服务器应用都无法满足人们需求,因为它们无法真正代表人类。为了让互联网服务更加便捷有效,就需要引入更多的人机交互,比如用户验证、授权等功能。

2.3 使用Firebase提供的认证服务,可以轻松地实现用户登录、注册功能吗?

是的!Firebase为iOS开发者提供了基于Google认证服务的完整解决方案,而且还可以很好地兼容第三方登录,使得整个验证体系相当强大。只需按照文档或示例代码配置好Firebase,就可以轻松地实现用户登录、注册功能。

2.4 为什么选择Firebase作为项目中的用户验证服务?

首先,Firebase是一个非常完备、可靠的产品。它的认证服务保证了用户信息的安全和私密,并且在验证过程中也有很多优化措施,可以避免暴力攻击或被盗号。其次,它可以与第三方登录(如Facebook、Twitter、GitHub)无缝集成,因此用户可以使用其他社交账号直接登录你的应用。第三,它提供有限额的免费额度,如果用户量较大,可以通过升级付费服务来增加认证服务的能力。最后,它还有一些独特的特性,例如推送通知、匿名统计等,可以为你的应用提供更好的用户体验。总而言之,选择Firebase作为项目中的用户验证服务是个不错的选择!

2.5 用户验证流程和原理?

用户验证过程如下:

  1. 用户注册或登陆应用程序;
  2. 如果用户第一次使用该应用程序,则会要求输入用户名、邮箱、密码和手机号码等个人信息;
  3. Firebase后台会对这些信息进行验证,确保信息的有效性;
  4. 如果验证成功,就会生成一个唯一的身份令牌,之后每次用户访问都会通过这个身份令牌确认身份;
  5. 在用户验证结束后,即可正常使用应用功能。

值得注意的是,用户身份验证并不是只有Firebase才有的功能,传统的网站或服务器应用也可以提供类似的服务。不过,由于各公司使用的技术栈不同,所以流程也可能会有所差别。

2.6 提供安全保障的用户授权机制?

用户授权是指用户在注册或登录成功后,得到授予特定权限的过程。授权机制的目的就是给用户提供足够的访问控制,限制他们的行为并防止恶意的或无效的请求。有两种类型的授权方式:

  1. 角色授权:管理员可以给某些用户分配不同的角色,根据角色的不同设置不同的权限。比如,普通用户只能查看自己的账户信息,管理员可以管理所有用户的权限。

  2. 条件授权:管理员可以指定特定条件(如IP地址、设备类型、登录时间等),只有满足这些条件的用户才能访问特定资源。

虽然提供用户授权功能并非绝对必要,但它可以极大地提高用户的体验,最大程度地减少风险。

2.7 可扩展性和容错率如何影响用户验证性能?

对于大型应用来说,验证服务的可扩展性和容错率至关重要。由于用户验证涉及到大量的数据存储、计算等操作,所以性能的瓶颈往往是网络连接或者数据库访问速度。

当用户量增长到一定数量级时,应当考虑以下几种优化方法:

  1. 缓存:把频繁访问的数据缓存在本地,降低数据库访问的压力。

  2. 分片:将用户分组存储,减少单台数据库服务器负载。

  3. 异步处理:采用异步的方式处理用户请求,充分利用CPU资源,提升响应能力。

另一方面,应当考虑容错机制,即定期检查和修复异常情况,提升服务的稳定性。

2.8 设计好的用户验证系统的架构图?

下图展示了一个设计好的用户验证系统的架构图,包含了几个主要模块:

  1. 服务端:包含了一个身份验证服务器(Auth Server),用来验证用户的身份信息。

  2. 客户端库:包含了用于与Auth Server通信的客户端库。

  3. 用户设备:包含了需要进行用户验证的用户设备。

  4. 应用:包含了一个需要用户验证的应用程序。

图中,应用的前端界面会调用客户端库,向Auth Server发送请求。Auth Server会对用户提交的信息进行验证,然后返回一个令牌。前端界面会保存这个令牌,并在后续的请求中带上它。这样,Auth Server和应用之间的通信就已经完成了。

3.具体代码实例和解释说明

3.1 配置Firebase项目

填写应用名称并点击“添加应用”,创建新的应用:

在应用概览页面中,找到“Firebase SDK snippet”,复制下面的代码片段:

将apiKey、projectId等字段替换成你自己项目对应的信息,然后将此代码片段加入你的HTML文件中。注意:这里假设您的项目域名为

3.2 创建用户验证视图控制器

创建LoginViewController.swift并添加以下代码:

import UIKitimport Firebaseclass LoginViewController: UIViewController {    @IBOutlet weak var usernameField: UITextField!    @IBOutlet weak var passwordField: UITextField!    override func viewDidLoad() {        super.viewDidLoad()        // [START initialize_auth]        let auth = Auth.auth()        // [END initialize_auth]        // Listen for auth state changes        NotificationCenter.default.addObserver(self, selector:#selector(handleAuthStateChange), name:.FIRAuthStateChanged, object: nil)    }    deinit {        NotificationCenter.default.removeObserver(self)    }    @objc func handleAuthStateChange() {        if Auth.auth().currentUser!= nil {            dismiss(animated: true, completion: nil)        } else {            print("User signed out")        }    }    @IBAction func loginPressed(_ sender: AnyObject) {        guard!usernameField.text?.isEmpty?? false &&             !passwordField.text?.isEmpty?? false else {                  return        }        // Sign in user with email and password        DispatchQueue.main.async {            self.loginWithEmailAndPassword()        }    }    private func loginWithEmailAndPassword() {        let email = usernameField.text! as NSString        let password = passwordField.text! as NSString        Auth.auth().signIn(withEmail: email, password: password) { (error) in            if error!= nil {                print("\(error!.localizedDescription)")            } else {                print("Successfully logged in!")            }        }    }}

首先,导入Firebase SDK和UIKit框架。然后,定义两个属性:usernameField,用来输入用户名;passwordField,用来输入密码。

viewDidLoad()方法中,初始化了一个新的Auth对象,并监听Auth对象的状态变化。

loginPressed()方法中,先判断用户名和密码是否都不为空。如果为空,则返回。否则,调用loginWithEmailAndPassword()方法进行用户登录。

loginWithEmailAndPassword()方法通过传入的用户名和密码,调用signIn(withEmail:password:completion)方法,尝试用电子邮件地址和密码进行登录。如果登录失败,打印错误信息;如果登录成功,打印一条成功日志。

3.3 实现用户注册功能

修改LoginViewController.swift的代码如下:

//...@IBAction func registerPressed(_ sender: AnyObject) {    guard!usernameField.text?.isEmpty?? false &&         !passwordField.text?.isEmpty?? false else {              return    }    DispatchQueue.main.async {        self.registerNewAccount()    }}private func registerNewAccount() {    let email = usernameField.text! as NSString    let password = passwordField.text! as NSString    Auth.auth().createUser(withEmail: email, password: password) { (error, _) in        if error!= nil {            print("\(error!.localizedDescription)")        } else {            print("Successfully registered new account!")        }    }}

registerPressed()方法中,先判断用户名和密码是否都不为空。如果为空,则返回。否则,调用registerNewAccount()方法进行新用户注册。

registerNewAccount()方法通过传入的用户名和密码,调用createUser(withEmail:password:completion)方法,尝试创建一个新的用户帐户。如果注册失败,打印错误信息;如果注册成功,打印一条成功日志。

注意:当用户注册成功后,默认情况下不会自动登录,需要手动调用signIn(withEmail:password:completion)方法进行登录。

3.4 添加退出登录按钮

修改LoginViewController.swift的代码如下:

//...override func viewDidLoad() {    //...    // Set up logout button    guard let logoutButton = navigationItem.rightBarButtonItem else {        fatalError("Expected 'logout' bar button item but not found.")    }    logoutButton.title = "Logout"    logoutButton.enabled = Auth.auth().currentUser!= nil    NotificationCenter.default.addObserver(self, selector:#selector(handleAuthStateChange), name:.FIRAuthStateChanged, object: nil)}deinit {    NotificationCenter.default.removeObserver(self)}func applicationWillResignActive(_ application: UIApplication) {    Auth.auth().signOut()}@objc func handleAuthStateChange() {    guard let currentUser = Auth.auth().currentUser else {        updateUIForSignedOutUser()        return    }    switch currentUser.providerType {    case.email:        updateUIForUserLoggedInUsingEmail()    default:        break    }}func updateUIForSignedOutUser() {    let currentVC = self.navigationController?.visibleViewController    if let navController = currentVC as? UINavigationController,       let viewControllers = navController.viewControllers,       viewControllers[0].isKind(of: LoginViewController.self) {           return    }    logoutButton.setTitle("Sign In", for:.normal)    logoutButton.setEnabled(true)}func updateUIForUserLoggedInUsingEmail() {    let currentVC = self.navigationController?.visibleViewController    if let navController = currentVC as? UINavigationController,       let viewControllers = navController.viewControllers,       viewControllers[0].isKind(of: LoginViewController.self) {           return    }    logoutButton.setTitle("Logout \(Auth.auth().currentUser!.email!)", for:.normal)    logoutButton.setEnabled(true)}

viewDidLoad()方法中,设置一个右侧的退出按钮。通过调用Auth.auth().currentUser方法判断当前用户是否已登录。如果已登录,则设置按钮的标题为“Logout”,并禁用按钮;否则,设置按钮的标题为“Sign In”,并启用按钮。并在NotificationCenter监听Auth对象状态变化。

applicationWillResignActive(_:)方法中,调用Auth.auth().signOut()方法,注销当前用户。

handleAuthStateChange()方法中,更新右侧按钮的标题和可用性。当用户退出登录时,更新标题为“Sign In”,并启用按钮;当用户登录时,更新标题为“Logout”加上用户的电子邮箱地址,并禁用按钮。注意:这里只是简单的实现了用户退出登录的逻辑。

3.5 添加第三方登录支持

登录支持电子邮件和密码,还可以支持第三方登录,如Facebook、Twitter、GitHub等。首先,需要到Firebase控制台中添加对应的第三方应用,并获取相应的配置信息。修改LoginViewController.swift的代码如下:

//...let providers: [Provider] = [   .google(),   .facebook(),   .twitter(),   .github()]//...private func setupThirdPartyLogins() {    // Register third party authentication providers    Auth.auth().providers = providers    // Present an alert controller allowing the user to select their provider    let alertController = UIAlertController(title: nil, message: "Please sign in using one of your social accounts:", preferredStyle:.alert)    for provider in providers {        let action = UIAlertAction(title: "\(provider.id)".capitalized, style:.default) { (_) in            switch provider {            case.google():                Auth.auth().signIn(with:.appleID(), presenting: self)            case.facebook():                Auth.auth().signIn(with:.facebook(), presenting: self)            case.twitter():                Auth.auth().signIn(with:.twitter(), presenting: self)            case.github():                Auth.auth().signIn(with:.github(), presenting: self)            }        }        alertController.addAction(action)    }    let currentVC = self.navigationController?.visibleViewController    present(alertController, animated: true, completion: nil)}extension LoginViewController: FUIAuthPresentationDelegate {    public func authUI(_ authUI: FUIAuth, didSignInWith provider: Provider, rawUserInfo: [String : String]) {        DispatchQueue.main.async {            self.dismiss(animated: true, completion: nil)            if let userInfo = try? JSONSerialization.jsonObject(with: Data(rawUserInfo.data?? ""), options: []) as? [String : Any],               let accessToken = (userInfo["accessToken"] as? String)?.addingPercentEncoding(withAllowedCharacters:.urlQueryAllowed)??"",               let expiresAt = Double((userInfo["expirationDate"] as? Date)?.timeIntervalSince1970) {                Auth.auth().setPersistenceEnabled(true) {                    _ in                    Auth.auth().signIn(withCustomToken: accessToken, expiresAt: Date(timeIntervalSince1970: expiresAt)) {                        (error) in                        if error == nil {print("User signed in with custom token from \(provider.id).")                        } else {print("\(error!.localizedDescription)")                        }                    }                }            }        }    }}

首先,定义一个数组变量providers,里面包含了支持的三方登录方式。然后,在viewDidLoad()方法中,调用setupThirdPartyLogins()方法,设置登录选项。

setupThirdPartyLogins()方法先构造一个UIAlertController,显示一个标题和消息,询问用户使用哪一种第三方登录方式登录。对于每一种登录方式,构造一个UIAlertAction,用以触发对应的登录事件。调用signIn(with:presenting:)方法,启动登录流程。

当第三方登录成功后,回调authUI(_:didSignInWith:rawUserInfo:)方法,解析得到第三方提供的身份令牌和过期时间戳,构造自定义令牌,并调用signIn(withCustomToken:expiresAt:completion:)方法,尝试用自定义令牌登录。如果登录成功,打印成功日志;如果登录失败,打印错误信息。

3.6 实现用户授权机制

授权是保护应用资源的关键环节。当用户登录成功后,可以通过登录信息来获得相关的权限,比如管理员权限、普通用户权限等。本文以管理员权限为例,展示如何实现授权机制。

首先,修改LoginViewController.swift的代码如下:

//...var roles: [Role] = []//...enum Role: Int, CaseIterable {    case admin    case regular}struct UserData {    var userId: String    var role: Role    var otherInfo: [String: Any]?}var usersDatabase: DatabaseReference?lazy var db: DatabaseReference {    guard let reference = usersDatabase else {        fatalError("Could not access database.")    }    return reference}//...func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {    //...    // Configure Firestore database    FirebaseFirestore.firestore().settings.isPersistenceEnabled = true    // Get references to user data in Firestore database    guard let firestoreDb = PersistenceManager.firestoreDb,          let mainUserId = Auth.auth().currentUser?.uid else {                      fatalError("Could not get persistence manager or current user ID.")    }    let userDataCollection = firestoreDb.collection("users").document(mainUserId)    usersDatabase = userDataCollection    fetchUserData { [weak self] (userData) in        if let userData = userData {            self?.roles = [userData.role]        }        self?.updateUI()    }    //...}func fetchUserData(completion: @escaping (_ userData: UserData?) -> Void) {    guard let documentSnapshot = db.get() else {        completion(nil)        return    }    guard let userDataDict = documentSnapshot.data() as? [String: Any] else {        completion(nil)        return    }    guard let roleRawValue = userDataDict["role"],          let roleInt = Int(roleRawValue as Any) else {                      completion(nil)                      return    }    guard let role = Role(rawValue: roleInt),          let userId = documentSnapshot.reference.parent?.id,          let otherInfoDict = userDataDict["otherInfo"] as? [String: Any] else {                      completion(nil)                      return    }    let userData = UserData(userId: userId, role: role, otherInfo: otherInfoDict)    completion(userData)}func updateUI() {    if let selectedIndex = Array(Role.allCases).firstIndex(where: { $0 == roles.first }),       let indexPath = IndexPath(item: selectedIndex, section: 0),       let tableView = view.subviews.filter({$0 is UITableView}).first as? UITableView,       let cell = tableView.cellForRow(at: indexPath) as? UITableViewCell,       let label = cell.contentView.subviews.first as? UILabel {        label.textColor =.red    }    if roles.contains(.admin) {        navigationItem.leftItemsSupplementBackButton = false    } else {        navigationItem.leftItemsSupplementBackButton = true    }}

首先,定义了一个枚举Role,用来表示用户角色,包含两个选项:adminregular。然后,定义了一个结构体UserData,用来存储用户ID、角色和其他信息。

fetchUserData(completion:)方法中,读取用户数据集合中的当前用户的数据,解析出用户ID、角色和其他信息。并构造一个UserData对象,并传递给完成闭包。

updateUI()方法中,动态设置导航栏左边按钮的颜色。如果当前用户角色中包含管理员权限,则取消隐藏它;否则,隐藏左边按钮。

3.7 优化用户验证性能

当用户量较大时,应当考虑优化用户验证的性能。首先,修改LoginViewController.swift的代码如下:

//...lazy var cache: NSCache = { [unowned self] in    let cache = NSCache()    return cache}()override func viewDidLoad() {    //...    usersDatabase?.addSnapshotListener { (snapshot, error) in        guard snapshot!= nil, error == nil else { return }        let query = Query(reference: snapshot!.reference!)        query.getDocuments { (querySnapshot, error) in            guard error == nil else { return }            self.cache.removeAllObjects()            for doc in querySnapshot! {                let userData = UserData(userId: doc.reference.parent?.id!,            role:.admin,    // Assume all users are admins for simplicity            otherInfo: ["displayName": doc["displayName"]])                self.cache.setObject(userData, forKey: doc.reference.path)            }        }    }    //...}//...private func getUserByUID(uid: String, completion: @escaping (_ userData: UserData?) -> ()) {    let path = uid    let cachedUserData = cache.object(forKey: path) as? UserData    if let userData = cachedUserData {        completion(userData)        return    }    guard let documentSnapshot = db.child(path).get() else {        completion(nil)        return    }    guard let displayName = documentSnapshot.data()["displayName"] as? String,          let roleRawValue = documentSnapshot.data()["role"],          let roleInt = Int(roleRawValue as Any) else {                      completion(nil)                      return    }    guard let role = Role(rawValue: roleInt) else {        completion(nil)        return    }    let userData = UserData(userId: uid,                           role: role,                           otherInfo: ["displayName": displayName])    cache.setObject(userData, forKey: path)    completion(userData)}func validateCurrentUser() {    if let currentUserID = Auth.auth().currentUser?.uid {        getUserByUID(currentUserID) { [weak self] (userData) in            guard let userData = userData else { return }            self?.roles = [userData.role]            self?.updateUI()        }    }}

首先,创建了一个NSCache对象,用来缓存用户数据。在viewDidLoad()方法中,监听用户数据集合的变化,并把用户数据缓存起来。

getUserByUID(uid:completion:)方法中,优先从缓存中获取用户数据,否则从数据库中读取用户数据。并构造一个UserData对象,并传递给完成闭包。

validateCurrentUser()方法中,优先从缓存中获取当前用户的数据,否则从数据库中读取当前用户的数据。并刷新roles数组,并调用updateUI()方法。

4.未来发展趋势与挑战

4.1 更多第三方登录方式

目前,Firebase仅支持微信、微博、QQ、Facebook、GitHub等五种主要的第三方登录方式,但随着社会发展,更多的第三方登录方式将会涌入市场,比如华为、百度、微软、腾讯、阿里巴巴等。

为此,我们建议构建适合各种场景的统一登录方式,支持众多的第三方登录方式。为达到这一目标,我们建议基于Firebase基础能力,结合第三方SDK开发,构建开放的认证系统,同时开放API接口,让开发者可以轻松接入各个第三方登录SDK。

另外,除了授权管理之外,还可以在用户验证流程中加入一系列的安全机制,比如二次验证码校验、滑动验证、手机号验证等。在这些机制的帮助下,用户登录过程将变得更加安全、可信。

4.2 密钥泄露与恢复机制

由于Firebase的身份验证依赖于密钥信息,如果密钥泄露或被攻击者获取,那么攻击者就可以冒充受害者,冒充受害者进行交易、买卖等,导致严重经济损失。因此,为防范密钥泄露,我们建议为Firebase分配多个主密钥,并且每个用户只能拥有一个主密钥。当某个主密钥泄露时,我们可以通过将泄露的密钥标记为不可用,并对其他主密钥重新加密用户数据,来降低用户的损失。

4.3 身份验证漏洞与滥用问题

随着移动应用的普及,越来越多的用户发现了安全漏洞,这些安全漏洞可能会导致用户信息泄露、手机被盗、钱财被盗等一系列的问题。为此,我们建议 Firebase 团队对整个身份验证流程做好评估和监控工作,确保其安全性和可用性。如发现任何漏洞,Firebase 将积极响应,采取紧急补救措施。

来源地址:https://blog.csdn.net/universsky2015/article/details/132843634

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-移动开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯