使用 AWS Cognito 对 Web 和 iOS 应用程序进行用户身份验证(第 1 部分)

已发表: 2022-03-10
快速总结↬开发人员和组织都在寻找一种方法来提高移动解决方案的灵活性。 人们希望减少从构思到测试的时间。 作为一名开发人员,我经常遇到一个可以减缓移动假设初始构建的障碍:用户管理。

多年来,我从零开始构建了至少三个用户管理系统。 大部分方法可以基于样板,但总是有一些关键项目需要为特定客户定制。 这足以引起人们的关注,一整类用户管理、身份验证和授权服务都应运而生,以满足这一需求。 像 Auth0 这样的服务拥有基于用户和身份管理的完整解决方案,开发人员可以与之集成。

提供此功能的一项服务是 Amazon Web Services (AWS') Cognito。 Cognito 是一种工具,可让用户注册和登录您创建的 Web 和移动应用程序。 除此功能外,它还允许离线存储用户数据,并提供此数据的同步。 正如亚马逊所说,“借助 Amazon Cognito,您可以专注于创建出色的应用体验,而不必担心构建、保护和扩展解决方案来处理用户管理、身份验证和跨设备同步。”

低估轮播

旋转木马真的不值得他们多年来获得的坏名声。 它们可以证明是非常有效的,并且有多种形状和尺寸。 阅读相关文章 →

去年,亚马逊在其 Cognito 服务中引入了自定义用户池。 现在,此功能提供了我和其他开发人员所需的东西,以便拥有一个完整的、可定制的、跨平台的用户管理系统,并具有适应大多数用例所需的灵活性。 要了解原因,我们需要快速了解用户管理是什么以及它解决了哪些问题。

跳跃后更多! 继续往下看↓
AWS 认知
Cognito 自定义用户池图(查看大图)

在本文中,我们将花费大部分时间来完成为我们的需求配置用户池的过程。 然后,我们将此用户池与 iOS 应用程序集成,并允许用户登录并获取与其用户帐户关联的属性。 到最后,我们将有一个有限的演示应用程序,但它处理用户管理的核心。 此外,在这到位之后,会有一篇后续文章对此进行更深入的探讨。

我们需要从用户管理中得到什么?

如果您有一个移动或网络应用程序,那么您在用户管理方面究竟需要什么? 虽然用户登录可能是您首先想到的,但我们不能止步于此。 如果我们想要一个适用于大多数 Web 和移动应用程序用例的灵活用户管理系统,它需要具有以下功能:

  • 用户名和密码登录;
  • 安全密码散列和存储;
  • 密码更改;
  • 密码策略和验证;
  • 用户生命周期触发器(欢迎电子邮件、再见电子邮件等);
  • 用户属性(名字、姓氏等);
  • 每个用户所需的配置和可选属性;
  • 处理忘记的密码;
  • 通过短信验证电话号码;
  • 电子邮件验证;
  • 基于权限的 API 访问端点;
  • 在移动设备上安全存储访问令牌;
  • 移动设备用户属性的离线存储;
  • 在线和离线状态的用户属性同步;
  • 多因素身份验证。

虽然用户管理起初可能看起来像一个登录系统,但功能必须远远超出此范围,才能使系统真正灵活地处理大多数用例。 这显然远远超出了用户名和密码。

此处需要指出另外一项:安全性。 任何用户管理系统的要求之一是需要对整个系统的安全性进行持续评估。 许多自定义用户管理系统存在根本没有得到纠正的漏洞。 去年,Dropbox、Dailymotion、Twitter 和 Yahoo 等公司的用户管理系统出现了安全漏洞。 如果您选择构建自定义解决方案,那么您就需要保护您的系统。

进入亚马逊认知

Amazon Cognito 是一项托管服务,可让您将灵活且可扩展的用户管理系统集成到您的 Web 和移动应用程序中。 Cognito 提供了两种不同的方式来使用该服务:联合身份,允许通过 Facebook 等社交网络登录,以及用户池,为特定应用程序或应用程序套件提供完全自定义的用户管理功能。

如果您希望用户能够使用 Facebook(或 Google、Amazon 等)登录,联合身份非常有用,但这意味着用户管理流程的一部分将外包给 Facebook。 虽然在某些情况下这可能是可以接受的,但用户可能不想将他们的 Facebook 帐户连接到您的应用程序。 此外,您可能希望直接管理更多用户的生命周期,为此,联合身份没有那么灵活。 就今天的文章而言,我们将重点关注用户池,因为它们提供了适用于大多数用例的强大用户管理平台所需的灵活性。 通过这种方式,您将拥有一种可用于大多数项目的方法。

因为这是一项 AWS 服务,所以使用 Cognito 还有其他好处。 Cognito 可以与 API Gateway 集成,以提供一种基于从 Cognito 登录返回的令牌授权 API 访问的轻松方式。 此外,如果您已经将其他 AWS 服务用于您的移动应用程序,您可以使用您的用户池作为您的 AWS 凭证的身份提供商。

与任何其他 AWS 服务一样,都会产生成本。 Cognito 的定价基于每月活跃用户 (MAU)。 对于大多数开发人员来说,好消息是,使用自定义用户池时,有一个无限期的免费层级,其上限为 50,000 个 MAU。 如果您有一个大型应用程序,这将为您提供大量用户来试用一种新的用户管理方法。 但是,我怀疑你们中的许多人的体验永远不会超过 50,000 个用户。 在这种情况下,核心用户管理几乎是免费的。 唯一的例外是您将在用户管理流程中使用的其他 AWS 服务,例如 Lambda、SNS 和 S3。

创建用户池

将用户池集成到您的移动应用程序的第一步是创建一个 Cognito 用户池。 这将为我们提供插入示例应用程序所需的配置值。 要创建新用户池,请完成 Amazon 的 Cognito 控制台中提供的向导。

让我们来看看创建用户池的过程。 我必须警告你,这是一个漫长的过程。 在许多方面,这是一件好事,因为它显示了灵活性。 但是,您会想喝杯咖啡并为此做好准备。

1.名称

创建用户池的第一步包括为您的用户池设置名称并选择您将采用的创建用户池的方法。 您可以查看默认设置或“逐步执行”设置。 因为我们希望对如何配置用户池有一个很好的工作知识,所以选择“逐步执行设置”选项。

用户池创建步骤 1
创建用户池的第一步(查看大图)

2. 属性

配置属性需要一些思考。 对于每个用户池,您需要确定哪些属性将存储在系统中,哪些是必需的。 因为系统将强制执行所需的值,所以您无法在以后更改它。 此处最好的方法是根据需要仅在此处标记真正重要的值。 此外,如果您希望用户能够使用他们的电子邮件地址登录,请务必将该电子邮件地址标记为别名。

如果您想包含自定义值,您也需要在此处执行此操作。 每个自定义值都有一个类型、可选的验证规则和一个可变(可更改)或不可变(不可更改)的选项。 有 25 个自定义属性的硬性限制。

最后,这里需要说明一下用户名。 每个用户的用户名值是不可变的(不可更改)。 这意味着,在大多数情况下,自动生成此值是有意义的。 这就是存在“首选用户名”值的原因。 如果您希望用户拥有可以编辑的用户名值,只需将“首选用户名”属性标记为别名。 如果您希望用户只需使用他们的电子邮件地址登录,请务必将“电子邮件”属性标记为必填项和别名。

对于我们的演示应用程序,我选择将“电子邮件”、“名字”和“姓氏”设为必需。

用户池创建步骤 2
为用户池配置用户属性(查看大图)

3. 政策

配置属性后,您将能够为帐户配置策略。 要配置的第一个策略是密码策略。 该策略允许您配置长度以及是否需要数字、特殊字符、大写字母或小写字母。 此策略将在用户输入的密码以及管理员分配给用户的密码上强制执行。

下一个政策与用户注册有关。 对于公共应用程序,您可能希望允许用户自己注册。 但是,根据应用程序的类型,您可能希望限制注册并让系统仅限邀请。 此外,您还必须配置这些邀请在不使用时的过期时间。

对于我们的演示应用程序,我选择仅使用默认值,但我不希望用户能够自行注册。 有了这些值,我们就可以进行验证。

用户池创建中的第 3 步
为用户池配置策略(查看大图)

4. 验证

验证步骤允许您设置多因素身份验证以及电子邮件和电话验证。 虽然在控制台中设置此功能相对容易,但请注意,如果您想验证电话号码或使用多重身份验证,则需要请求增加 AWS SNS 的支出。

对于我们的演示应用程序,我选择仅使用默认值。

用户池创建中的第 4 步
为用户池配置验证(查看大版本)

5.消息自定义

此步骤允许您自定义用户池将发送的电子邮件和 SMS 消息,以及“发件人”和“回复”电子邮件地址。 出于我们演示应用程序的目的,我将在此处保留默认值并继续。

用户池创建中的第 5 步
为用户池配置生命周期消息(查看大图)

6. 标签

如果您是 AWS 新手,您可能不需要指定任何标签。 但是,如果您的组织经常使用 AWS,标签提供了一种使用 IAM 分析支出和分配权限的方法。 例如,一些组织按环境(开发、登台、生产)和项目指定标签。

无论您在此步骤中输入什么内容,都不会影响我们的演示应用程序。

用户池创建中的第 6 步
为用户池添加标签(查看大图)

7. 设备

下一步允许您定义用户池是否会记住您用户的设备。 这是一个额外的安全步骤,可让您查看特定帐户已登录的设备。 当您利用多重身份验证 (MFA) 时,这具有额外的价值。 如果设备被记住,您可以选择在每次登录时不需要 MFA 令牌。

出于演示应用程序的目的,我选择将值设置为“始终”。

用户池创建步骤 7
为用户池配置设备处理(查看大版本)

8. 应用客户端

对于您要为其使用用户池的每个应用程序(例如 iOS 应用程序、Web 应用程序、Android 应用程序等),您应该创建一个应用程序。 但是,您可以在创建用户池后返回并创建这些,因此目前还没有迫切需要添加所有这些。

每个应用程序都有几个可以配置的值。 对于这个演示应用程序,我们将为应用程序命名,然后保留默认值。 接下来,您可以配置每个应用程序可以读取和写入的用户属性。

用户池创建中的第 8 步
为用户池配置客户端应用程序(查看大图)

您可以在此步骤中设置您喜欢的任何值,只要电子邮件地址、姓氏和名字都可以被应用程序读取和写入。 在继续之前,请务必单击“创建应用程序客户端”选项。

9. 触发器

借助触发器,您可以使用 Lambda 函数来完全自定义用户生命周期流程。 例如,如果您只希望具有来自您公司域的电子邮件地址的用户能够注册,您可以为“预注册”触发器添加一个 Lambda 函数来执行此验证并拒绝任何注册请求不通过。

对于我们的演示应用程序,我不会添加任何触发器。

用户池创建中的第 9 步
为用户池配置触发器(查看大图)

10. 回顾

我意识到这似乎是一个漫长而艰巨的过程。 但请记住,创建用户池的每个步骤都具有灵活性,可以让解决方案适应更多用例。 现在,您一直在等待听到的消息:这是最后一步。

只需检查设置以确保您已为演示应用程序正确配置它们。 在此屏幕中,您可以返回并编辑之前的任何设置。 创建用户池后,某些配置值(例如必需的属性)将无法更改。

创建新用户池后,您现在可以继续使用适用于 iOS 的 AWS 开发工具包将它们集成到示例 iOS 应用程序中。

用户池创建中的第 10 步
创建前对用户池的最终审查(查看大图)

为您的用户池设置您的 iOS 应用程序

我创建了一个与 Cognito 集成的示例 iOS 应用程序,以允许用户登录、注销、输入他们的名字和姓氏以及设置密码。 对于这个初始演示,不包括用户注册,所以我使用 Cognito 的控制台添加了一个新用户进行测试。

此应用程序的代码可以在我的 GitHub 存储库中找到。

配置依赖

此应用程序使用 CocoaPods 来管理依赖项。 此时,唯一的依赖项是与 Cognito 用户池相关的 AWS iOS 开发工具包的特定部分。

(对 CocoaPods 的完整描述超出了本文的范围,但是,如果您对这个概念不熟悉,CocoaPods 网站上的资源将帮助您启动和运行。)

此应用程序的 Podfile 的内容如下所示:

 source 'https://github.com/CocoaPods/Specs.git' platform :ios, '10.0' use_frameworks! target 'CognitoApplication' do pod 'AWSCore', '~> 2.5.5' pod 'AWSCognitoIdentityProvider', '~> 2.5.5' end

假设您的机器上安装了 CocoaPods,您只需运行pod install ,就会为您安装必要的依赖项。

用户池配置

下一步是包含用户池和客户端应用程序的值。 演示应用程序配置为使用文件CognitoApplication/CognitoConfig.plist ,从中提取此信息。 需要定义四个值:

  • region (字符串)
    这是您创建用户池的区域。 这需要是标准区域标识符,例如us-east-1ap-southeast-1
  • poolId (字符串)
    这是您创建的用户池的 ID。
  • clientId (字符串)
    这是作为附加到用户池的应用程序的一部分配置的clientId
  • clientSecret (字符串)
    这是配置为附加到用户池的应用程序的一部分的clientSecret

使用该文件和适当的值,可以启动演示应用程序。 如果在启动期间发生任何异常,请确保您已包含下面显示的四个值中的每一个,并且该文件已放置在正确的目录中。

Xcode 中的 plist 配置
使用plist文件在 Xcode 中配置用户池(查看大图)

应用代理集成

与 Amazon Cognito 集成的核心发生在应用程序的AppDelegate中。 我们的第一步是确保我们已设置日志记录并已连接到我们的用户池。 作为该过程的一部分,我们将AppDelegate分配为用户池的委托。 对于这个基本示例,我们可以将此逻辑保留在AppDelegate中。 对于较大的项目,在其他地方处理它可能是有意义的。

 func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { // set up logging for AWS and Cognito AWSDDLog.sharedInstance.logLevel = .verbose AWSDDLog.add(AWSDDTTYLogger.sharedInstance) // set up Cognito config self.cognitoConfig = CognitoConfig() // set up Cognito setupCognitoUserPool() return true } func setupCognitoUserPool() { // we pull the needed values from the CognitoConfig object // this just pulls the values in from the plist let clientId:String = self.cognitoConfig!.getClientId() let poolId:String = self.cognitoConfig!.getPoolId() let clientSecret:String = self.cognitoConfig!.getClientSecret() let region:AWSRegionType = self.cognitoConfig!.getRegion() // we need to let Cognito know which region we plan to connect to let serviceConfiguration:AWSServiceConfiguration = AWSServiceConfiguration(region: region, credentialsProvider: nil) // we need to pass it the clientId and clientSecret from the app and the poolId for the user pool let cognitoConfiguration:AWSCognitoIdentityUserPoolConfiguration = AWSCognitoIdentityUserPoolConfiguration(clientId: clientId, clientSecret: clientSecret, poolId: poolId) AWSCognitoIdentityUserPool.register(with: serviceConfiguration, userPoolConfiguration: cognitoConfiguration, forKey: userPoolID) let pool:AWSCognitoIdentityUserPool = AppDelegate.defaultUserPool() // we need to set the AppDelegate as the user pool's delegate, which will get called when events occur pool.delegate = self }

完成此配置后,我们需要为用户池配置委托方法。 我们正在实施的协议是AWSCognitoIdentityInteractiveAuthenticationDelegate 。 每当用户需要登录、重置密码或提供多因素身份验证代码,或者我们需要询问用户是否希望记住他们的设备时,都会调用此委托。 对于我们的示例,我们只需要实现startPasswordAuthenticationstartNewPasswordRequired方法:

 extension AppDelegate: AWSCognitoIdentityInteractiveAuthenticationDelegate { // This method is called when we need to log into the application. // It will grab the view controller from the storyboard and present it. func startPasswordAuthentication() -> AWSCognitoIdentityPasswordAuthentication { if(self.navigationController == nil) { self.navigationController = self.window?.rootViewController as? UINavigationController } if(self.loginViewController == nil) { self.loginViewController = self.storyboard?.instantiateViewController(withIdentifier: "LoginViewController") as? LoginViewController } DispatchQueue.main.async { if(self.loginViewController!.isViewLoaded || self.loginViewController!.view.window == nil) { self.navigationController?.present(self.loginViewController!, animated: true, completion: nil) } } return self.loginViewController! } // This method is called when we need to reset a password. // It will grab the view controller from the storyboard and present it. func startNewPasswordRequired() -> AWSCognitoIdentityNewPasswordRequired { if (self.resetPasswordViewController == nil) { self.resetPasswordViewController = self.storyboard?.instantiateViewController(withIdentifier: "ResetPasswordController") as? ResetPasswordViewController } DispatchQueue.main.async { if(self.resetPasswordViewController!.isViewLoaded || self.resetPasswordViewController!.view.window == nil) { self.navigationController?.present(self.resetPasswordViewController!, animated: true, completion: nil) } } return self.resetPasswordViewController! } }

需要注意的一个关键点是,这两种方法都返回一个实现特定协议的视图控制器。 例如, LoginViewController实现AWSCognitoIdentityPasswordAuthentication ,它有一个方法,该方法使用使用户能够完成登录过程所需的参数被调用。

身份验证流程

在演示应用程序中准备好所有这些部分后,您现在可以看到登录过程从头到尾工作。 应用程序的主视图显示用户名以及用户的名字和姓氏。 为此,需要执行以下步骤:

  1. AppViewController中,我们调用viewDidLoad方法中的fetchUserAttributes方法。 如果用户未登录,这将触发登录过程。
  2. AppDelegate中的startPasswordAuthentication方法将被触发。 此方法加载LoginViewController并呈现它。
  3. LoginViewControllergetDetails方法由 AWS 开发工具包调用。 这包括一个作为AWSTaskCompletionSource实例的对象,我们可以使用它来允许用户尝试登录。
  4. 当用户按下“登录”按钮时,我们将登录凭据传递给该对象。 然后这将调用didCompleteStepWithError方法,我们可以相应地处理结果。 如果没有错误,我们可以关闭视图控制器。
  5. 如果我们在控制台中创建了用户,我们将在此处处理另一个步骤。 因为我们给了用户一个临时密码,他们需要设置一个更永久的密码。 此外,因为我们将名字和姓氏设置为必需参数,所以我们也需要允许用户输入这些参数。 AWS 开发工具包将检测到这一点并调用AppDelegate中的startNewPasswordRequired方法。 这将显示ResetPasswordViewController并设置其AWSTaskCompletionSource实例。
  6. ResetPasswordViewController的工作方式几乎与LoginViewController相同。 我们只需要询问用户正确的值,然后提交这些值。 一旦这个过程成功完成,我们关闭视图控制器。
  7. 整个登录过程完成后,SDK 将安全地存储 Cognito 返回的令牌。 然后,我们最终将检索用户详细信息,我们可以使用这些信息将用户的用户名、名字和姓氏填充到AppViewController

具有身份验证的工作应用程序
工作示例应用程序,显示用户名和元数据

结论

虽然用户池设置过程可能有几个步骤,但这些步骤很容易导航。 此外,可能的配置量应该让您相信它可以支持大多数用例。 在 Universal Mind 的日常工作中,我曾与几个客户合作过,他们正在迁移他们现有的应用程序以利用 Cognito 为用户管理提供的功能。

无论您是否需要定期实施用户管理系统,这都是每个移动和 Web 开发人员都应该在他们的工具箱中拥有的工具。 在本系列的下一篇文章中,我们将通过实现一个功能更全的演示应用程序来进一步探索 Cognito 的功能,该应用程序实现了更多常见的用户管理用例。

通过一些练习,您可以在一天内设置一个满足所有这些用户管理用例的新应用程序,从而给所有朋友留下深刻印象。 这对于一天的工作来说已经很不错了。

链接和资源

  • 亚马逊认知
  • “开发者资源”,Amazon Cognito
  • AWS 移动开发工具包
  • “Swift 的 CocoaPods 教程:入门”,Joshua Greene,raywenderlich.com