Skip to main content
Cypress应用

Amazon Cognito 身份验证

info
您将学习到
  • 如何在 Cypress 中实现 Amazon Cognito 身份验证
  • 如何安全管理 Cypress 端到端测试场景中的身份验证流程

Amazon CognitoAmazon Web Services (AWS) 提供的身份验证服务。

Amazon Cognito 文档推荐使用 AWS Amplify 框架身份验证库 来与已部署的 Amazon Cognito 实例交互。通过该库,我们可以编程方式驱动用户的创建和身份验证流程。

本指南展示了使用 AWS Amplify 框架 所需的最简代码,以编程方式登录现有用户。

// 引入 'aws-amplify' 库
import Amplify, { Auth } from 'aws-amplify'

// 使用 Amazon Cognito 凭证配置 Auth 模块
Amplify.configure({
Auth: {
identityPoolId: 'XX-XXXX-X:XXXXXXXX-XXXX', // Amazon Cognito 身份池 ID
region: 'XX-XXXX-X', // Amazon Cognito 区域
},
})

// 调用 Auth.signIn 传入用户凭证
Auth.signIn(username, password)
.then((user) => console.log(user))
.catch((err) => console.log(err))

Amazon Cognito 设置

如果尚未设置,您需要先创建 AWS 账户

真实世界应用(RWA) 中提供了 Amazon Cognito 集成。克隆 Cypress Real World App 并安装 AWS Amplify CLI

npm install @aws-amplify/cli --global

真实世界应用(RWA) 通过 AWS Amplify 身份验证库 配置了可选的 Amazon Cognito 实例。AWS Amplify CLI 用于配置您的环境和云资源所需的基础设施。

首先运行 amplify init 命令初始化项目:

amplify init

然后运行 amplify push 命令在云端创建资源:

amplify push
info
注意

启动 Real World App 时使用 yarn dev:cognito 命令。

在 Cypress 中设置 Amazon Cognito 应用凭证

首先配置 Cypress 使用 .env 文件中的环境变量和 AWS Amplify CLI 构建过程中生成的 aws-exports.js 文件。

const { defineConfig } = require('cypress')
// 从 .env 文件加载环境变量
require('dotenv').config()
// AWS 导出配置
const awsConfig = require('./aws-exports-es5.js')

module.exports = defineConfig({
env: {
cognito_username: process.env.AWS_COGNITO_USERNAME,
cognito_password: process.env.AWS_COGNITO_PASSWORD,
awsConfig: awsConfig.default,
},
})

Amazon Cognito 身份验证自定义命令

有两种身份验证方式:

使用 cy.origin() 登录

创建 loginByCognito 自定义命令来执行登录流程:

cypress/support/auth-provider-commands/cognito.ts
// Amazon Cognito
const loginToCognito = (username: string, password: string) => {
Cypress.log({
displayName: 'COGNITO LOGIN',
message: [`🔐 Authenticating | ${username}`],
autoEnd: false,
})

cy.visit('/')

cy.origin(
Cypress.env('cognito_domain'),
{
args: {
username,
password,
},
},
({ username, password }) => {
cy.contains('Sign in with your email and password')
cy.get('input[name="username"]:visible').type(username)
cy.get('input[name="password"]:visible').type(password, {
log: false,
})
cy.get('input[name="signInSubmitButton"]:visible').click()
}
)

cy.wait(2000)
cy.contains('Get Started').should('be.visible')
}

Cypress.Commands.add('loginByCognito', (username, password) => {
return loginToCognito(username, password)
})

测试中使用该命令:

auth.spec.js
describe('Cognito, cy.origin() login', function () {
beforeEach(function () {
cy.task('db:seed')
cy.loginByCognito(
Cypress.env('cognito_username'),
Cypress.env('cognito_password')
)
})

it('shows onboarding', function () {
cy.contains('Get Started').should('be.visible')
})
})

使用 cy.session() 优化登录命令:

cypress/support/auth-provider-commands/cognito.ts
Cypress.Commands.add(
'loginByCognito, cy.origin() login',
(username, password) => {
cy.session(
`cognito-${username}`,
() => {
return loginToCognito(username, password)
},
{
validate() {
cy.visit('/')
cy.contains('Get Started').should('be.visible')
},
}
)
}
)

编程式登录

创建 loginByCognitoApi 命令通过 API 登录并设置 localStorage:

cypress/support/auth-provider-commands/cognito.ts
import Amplify, { Auth } from 'aws-amplify'

Amplify.configure(Cypress.env('awsConfig'))

Cypress.Commands.add('loginByCognitoApi', (username, password) => {
const log = Cypress.log({
displayName: 'COGNITO LOGIN',
message: [`🔐 Authenticating | ${username}`],
autoEnd: false,
})

log.snapshot('before')

const signIn = Auth.signIn({ username, password })

cy.wrap(signIn, { log: false }).then((cognitoResponse) => {
const keyPrefixWithUsername = `${cognitoResponse.keyPrefix}.${cognitoResponse.username}`

window.localStorage.setItem(
`${keyPrefixWithUsername}.idToken`,
cognitoResponse.signInUserSession.idToken.jwtToken
)

// 设置其他 token...
})

cy.visit('/')
})

测试中使用编程式登录:

describe('Cognito, programmatic login', function () {
beforeEach(function () {
cy.task('db:seed')
cy.loginByCognitoApi(
Cypress.env('cognito_username'),
Cypress.env('cognito_password')
)
})

it('shows onboarding', function () {
cy.contains('Get Started').should('be.visible')
})
})

适配 Amazon Cognito 应用进行测试

info
AWS 后端服务无需适配

当使用 AWS AppSync 或 API Gateway 时,它们可直接验证 Cognito JWT。如果是自托管后端(如 Express),则需要适配。

真实世界应用(RWA) 提供了 React 前端和 Express 后端的配置和代码。

适配后端

安装 express-jwtjwks-rsa 来验证 JWT:

backend/helpers.ts
import jwt from 'express-jwt'
import jwksRsa from 'jwks-rsa'

const awsCognitoJwtConfig = {
secret: jwksRsa.expressJwtSecret({
jwksUri: `https://cognito-idp.${awsConfig.aws_cognito_region}.amazonaws.com/${awsConfig.aws_user_pools_id}/.well-known/jwks.json`,
}),
issuer: `https://cognito-idp.${awsConfig.aws_cognito_region}.amazonaws.com/${awsConfig.aws_user_pools_id}`,
algorithms: ['RS256'],
}

export const checkCognitoJwt = jwt(awsCognitoJwtConfig).unless({
path: ['/testData/*'],
})

全局应用中间件:

backend/app.ts
import { checkCognitoJwt } from './helpers'

if (process.env.REACT_APP_AWS_COGNITO) {
app.use(checkCognitoJwt)
}

适配前端

创建 AppCognito.tsx 组件处理 Cognito 身份验证:

src/containers/AppCognito.tsx
import { Amplify, ResourcesConfig } from "aws-amplify";
import { fetchAuthSession, signInWithRedirect } from "aws-amplify/auth";

Amplify.configure(awsConfig as ResourcesConfig);

const AppCognito: React.FC = () => {
useEffect(() => {
if (!isLoggedIn) {
fetchAuthSession().then((authSession) => {
if (authSession?.tokens?.accessToken) {
authService.send("COGNITO", {
accessTokenJwtString: authSession.tokens.accessToken.toString(),
userSub: authSession.userSub!,
email: authSession.tokens.idToken!.payload.email,
});
} else {
void signInWithRedirect();
}
});
}
}, [isLoggedIn]);

if (!isLoggedIn) return null;
};

更新入口文件使用新组件:

src/index.tsx
if (process.env.REACT_APP_AWS_COGNITO) {
ReactDOM.render(
<Router history={history}>
<ThemeProvider theme={theme}>
<AppCognito />
</ThemeProvider>
</Router>,
document.getElementById('root')
)
}