Soto Cognito 身份验证工具包

Swift 6.0

Amazon Cognito 为您的 Web 应用程序提供身份验证、授权和用户管理。Soto Cognito 身份验证工具包是 Cognito 的 Swift 接口。

目录

与 Cognito 用户池一起使用

配置

首先,您需要创建一个 CognitoConfiguration 实例来存储所有配置信息,并创建您的 CognitoAuthenticatable 实例。

let awsClient = AWSClient(httpClientProvider: .createNew)
let cognitoIdentityProvider = CognitoIdentityProvider(client: awsClient, region: .euwest1)
let configuration = CognitoConfiguration(
    userPoolId: "eu-west-1_userpoolid",
    clientId: "23432clientId234234",
    clientSecret: "1q9ln4m892j2secreta0dalh9a3aakmpeugiaag8k3cacijlbkrp",
    cognitoIDP: cognitoIdentityProvider,
    adminClient: true
)
let authenticatable = CognitoAuthenticatable(configuration: configuration)

userPoolIdclientIdclientSecret 的值都可以在 Amazon Cognito 用户池 控制台中找到。AWSClient 是用于与 Amazon Web Services 通信的客户端,CognitoIdentityProvider 提供了 Cognito Identity Provider 用户池 API。这两个对象都由 Soto 库提供。在继续之前,建议您先阅读一下关于它们的文档,此处此处

adminClient 参数决定将使用哪一组 CognitoIdentityProvider 命令。如果设置为 true,则将使用命令的管理版本。这些版本需要带有 AWS 凭证的 AWSClient。您可以在此处找到有关提供凭证的更多详细信息。此外,像 createUser 这样的一些命令仅在 adminClient 设置为 true 时才可用。是否需要使用 adminClient 将由您在 AWS 控制台中为您的应用程序客户端设置的 authFlow 定义。如果您要将 adminClient 设置为 false,则可以按如下方式创建您的 AWSClient,因为您不需要 AWS 凭证。

let awsClient = AWSClient(
    credentialProvider: .empty, 
    httpClientProvider: .createNew
)

通常,命令的管理版本由服务器使用,而非管理版本由客户端软件使用。

创建 AWS Cognito 用户

假设我们有上面创建的 CognitoAuthenticatable 实例,则可以使用以下代码创建用户。如上所述,此函数需要配置 adminClient 设置为 true

let username = "johndoe"
let attributes: [String: String] = ["email": "user@email.com", "name": "John Doe", "gender": "male"]
return authenticatable.createUser(username: username, attributes: attributes)

您提供的属性应与您在 AWS Cognito 控制台中创建用户池时选择的属性相匹配。创建用户后,系统会向他们发送一封电子邮件,详细说明他们的用户名和随机生成的密码。

作为替代方案,您可以使用 signUp 函数,该函数接受 usernamepassword。这将向用户发送一封包含确认码的确认电子邮件。然后,您需要使用此确认码调用 confirmSignUp。要使此路径可用,您需要在用户池中设置“允许用户自行注册”标志。

使用用户名和密码进行身份验证

一旦您的用户在 signUp 情况下创建并确认。以下代码将从用户名和密码生成 JWT 身份验证令牌。此函数需要配置带有 AWS 凭证的 CognitoIdentityProvider,除非您传递设置为 falserequireAuthenticatedClient 参数。

let response = try await authenticatable.authenticate(
    username: username, 
    password: password,
    context: request
)
if case .authenticated(let authenticated) = response {
    let accessToken = authenticated.accessToken
    let idToken = authenticated.idToken
    let refreshToken = authenticated.refreshToken
...

访问令牌仅用于指示用户已被授予访问权限。它包含验证信息、用户名和一个主题 uuid,如果您不想使用用户名,可以使用该 uuid 来标识用户。令牌有效期为 60 分钟。idToken 包含有关用户身份的声明。它应包含附加到用户的所有属性。同样,此令牌仅在 60 分钟内有效。如果您收到 challenged 情况,则表示您遇到了登录挑战,必须先响应挑战才能收到身份验证令牌。请参阅下文

验证访问令牌是否有效

以下代码将验证令牌是否提供访问权限。

let response = try await authenticatable.authenticate(accessToken: token)
let username = response.username
let subject = response.subject
...

如果访问令牌已过期、不是由用户池颁发的或不是为应用程序客户端创建的,则此调用将返回失败的 Future 并出现未经授权的错误。

验证 ID 令牌的内容

ID 令牌包含用户的属性。由于这在不同项目之间有所不同,您必须提供一个自定义类来填充这些属性。该类需要继承自 Codable,并且 CodingKeys 需要反映 Amazon Web Services 提供的键。这些键在 OIDC 标准声明中定义。如果您有附加到用户的自定义属性,这些属性将以 "custom:" 为前缀。以下代码将从 ID 令牌中提取用户名、电子邮件、姓名和性别。

struct IdResponse: Codable {
    let email: String
    let username: String
    let name: String
    let gender: String
    
    private enum CodingKeys: String, CodingKey {
        case email = "email"
        case username = "cognito:username"
        case name = "name"
        case gender = "gender"
    }
}
let response = authenticatable.authenticate(idToken: token)
    .map { (response: IdResponse)->IdResponse in
        let email = response.email
        let username = response.username
        let name = response.name
        let gender = response.gender
        ...
        return response
}

注意:ID 令牌中的用户名标签是 "cognito:username"。

刷新 ID 令牌和访问令牌

为了避免每 60 分钟就要求用户提供用户名和密码,还提供了刷新令牌。您可以使用它来生成新的 ID 令牌和访问令牌,无论它们何时过期或即将过期。刷新令牌有效期为 30 天。不过,您可以在 Cognito 控制台中编辑此时长。

let response = try await authenticatable.refresh(
    username: username, 
    refreshToken: refreshToken, 
    context: request
)
let accessToken = response.authenticated?.accessToken
let idToken = response.authenticated?.idToken
...

响应身份验证挑战

有时,当您尝试验证用户名和密码或刷新令牌时,系统会返回挑战而不是身份验证令牌。例如,当有人首次登录时,他们需要先更改密码才能继续。在这种情况下,AWS Cognito 会返回一个新的密码挑战。当您使用新密码响应此挑战时,它会为您提供身份验证令牌。其他情况包括多因素身份验证。以下是响应更改密码请求的代码

let challengeName: CognitoChallengeName = .newPasswordRequired 
let challengeResponse: [String: String] = ["NEW_PASSWORD":"MyNewPassword1"]
let response = try await authenticatable.respondToChallenge(
    username: username, 
    name: challengeName, 
    responses: challengeResponse, 
    session: session, 
    context: request
)
let accessToken = response.authenticated?.accessToken
let idToken = response.authenticated?.idToken
let refreshToken = response.authenticated?.refreshToken
...

name 参数是一个枚举,包含所有挑战。responses 参数是一个字典,包含对挑战的输入。session 参数包含在身份验证请求返回给您的挑战中。如果挑战成功,您将收到 response.authenticated 作为响应。如果需要另一个挑战,您将在 response.challenged 中获得该挑战的详细信息。respondToChallenge 函数有针对新密码的自定义版本:respondToNewPasswordChallenge 和针对多因素身份验证的自定义版本:respondToMFAChallenge

创建用户池

如果您想将 Cognito 用户池与 Soto Cognito 身份验证库一起使用,则在创建 Cognito 用户池时需要进行一些设置。由于该库使用管理员级别的服务调用,因此设备跟踪不可用,请确保将设备记住功能设置为关闭。否则,您的刷新令牌将无法工作。

在为您的用户池创建应用程序客户端时,请确保启用“生成客户端密钥”。Soto Cognito 身份验证库会自动创建具有客户端密钥的用户池所需的密钥哈希。利用这一点是明智的。由于该库旨在在安全的后端服务器上工作,因此它使用 Admin no SRP 授权流程来验证用户身份。您还需要勾选“为身份验证启用管理员 API 的用户名密码身份验证 (ALLOW_ADMIN_USER_PASSWORD_AUTH)”,以确保身份验证正常工作。

有关 AWS Cognito 用户池的更多详细信息,您可以在此处找到 Amazon 的文档。

与 Cognito 身份池一起使用

Soto Cognito 身份验证可以用于与 Amazon Cognito 联合身份进行交互,从而允许您创建用于访问 AWS 服务的临时凭证。

配置

首先,您需要创建一个 CognitoIdentityConfiguration 实例,用于存储与 Amazon Cognito 联合身份进行交互的所有配置信息,并创建一个 CognitoIdentifiable 实例。

let cognitoIdentity = CognitoIdentity(client: awsClient, region: .euwest1)
let configuration = CognitoIdentityConfiguration(
    identityPoolId: "eu-west-1_identitypoolid"
    identityProvider: "provider"
    cognitoIdentity: cognitoIdentity
)
let identifiable = CognitoIdentifiable(configuration: configuration)

identityPoolId 可以从 AWS 控制台的“编辑身份池”部分获取。cognitoIdentity 是用于与 Amazon Web Services 通信的客户端。它由 Soto 库提供。identityProvider 是您在 AWS Cognito 身份池中设置的任何内容,用于提供身份验证详细信息。

访问 AWS 凭证

访问 AWS 凭证需要两个步骤。首先,您需要获取一个身份 ID,然后使用该身份 ID,您可以获取您的 AWS 凭证。这可以通过以下方式完成。

let identity = identifiable.getIdentityId(idToken: idToken)
return identifiable.getCredentialForIdentity(identityId: identity, idToken: token)

在您使用 Cognito 用户池的情况下,idToken 是您验证用户身份时返回的 idToken

安全远程密码

如果您从客户端使用用户名/密码身份验证,则最好使用安全远程密码 (SRP) 进行身份验证。SRP 是一种安全的基于密码的身份验证和密钥交换协议。它要求客户端向服务器表明它知道用户的密码,而无需实际将密码传递给服务器。此外,服务器不存储密码副本,而是存储一个验证器,该验证器可用于验证密码是否正确。AWS Cognito 中实现了一个版本,您可以按如下方式使用它

import SotoCognitoAuthenticationSRP

let response = authenticatable.authenticateSRP(
    username: username, 
    password: password,
    requireAuthenticatedClient: false
)

凭证提供程序

Soto Cognito 身份验证工具包提供了一个凭证提供程序,该提供程序结合了 Cognito 用户池身份验证和 Cognito Identity 来生成凭证。它将在需要时使用返回的刷新令牌刷新凭证。它的设置方式如下

let credentialProvider: CredentialProviderFactory = .cognitoUserPool(
    userName: username,
    authentication: .password(password),
    userPoolId: userPoolId,
    clientId: clientId,
    clientSecret: clientSecret,
    identityPoolId: identityPoolId,
    region: region,
    respondToChallenge: { challenge, parameters, error in
        // Respond to any challenges returned by userpool authentication
        // function parameters are
        // challenge: Challange type
        // parameters: Challenge parameters
        // error: Error returned from a previous respondToChallenge response
        switch challenge {
        case .newPasswordRequired:
            return try await respondToNewPassword()
        default:
            return nil
        }
    }
)
let client = AWSClient(credentialProvider: credentialProvider, httpClientProvider: .createNew)

authentication 参数允许您定义如何使用用户池进行身份验证。可能的选项包括 .password(需要密码)、.refreshToken(需要您已生成的刷新令牌),如果您已导入 SotoAuthenticationKitSRP,则 .srp 为您提供安全远程密码身份验证。

参考

SotoCognitoAuthenticationKit 的参考文档可以在此处找到。