Amazon Cognito 身份验证
您将学习到
- 如何在 Cypress 中实现 Amazon Cognito 身份验证
- 如何安全管理 Cypress 端到端测试场景中的身份验证流程
Amazon Cognito 是 Amazon Web Services (AWS) 提供的身份验证服务。
Amazon Cognito 文档推荐使用 AWS Amplify 框架身份验证库 来与已部署的 Amazon Cognito 实例交互。通过该库,我们可以编程方式驱动用户的创建和身份验证流程。
本指南展示了使用 AWS Amplify 框架 所需的最简代码,以编程方式登录现有用户。
- Amplify v5.x.x
- Amplify v6.x.x
// 引入 '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))
import { Amplify } from "aws-amplify";
import { fetchAuthSession, signIn } from "aws-amplify/auth";
Amplify.configure({
Auth: {
Cognito: {
userPoolId: "XX-XXXX-X_XXXXXXXXX",
userPoolClientId: "XXXXXXXXXXXXXXXXXXXXXXXXX",
// 或:
identityPoolId: 'XX-XXXX-X:XXXXXXXX-XXXX', // Amazon Cognito 身份池 ID
},
},
});
signIn({ username, password, { authFlowType: "USER_PASSWORD_AUTH" } })
// eslint-disable-next-line @typescript-eslint/no-unused-vars
.then((_signInOutput) => fetchAuthSession())
.then((authSession) => console.log(authSession))
.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
启动 Real World App 时使用 yarn dev:cognito
命令。
在 Cypress 中设置 Amazon Cognito 应用凭证
首先配置 Cypress 使用 .env
文件中的环境变量和 AWS Amplify CLI 构建过程中生成的 aws-exports.js
文件。
- cypress.config.js 文件
- cypress.config.ts 文件
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,
},
})
import { defineConfig } from 'cypress'
// 从 .env 文件加载环境变量
require('dotenv').config()
// AWS 导出配置
const awsConfig = require('./aws-exports-es5.js')
export default defineConfig({
env: {
cognito_username: process.env.AWS_COGNITO_USERNAME,
cognito_password: process.env.AWS_COGNITO_PASSWORD,
awsConfig: awsConfig.default,
},
})
Amazon Cognito 身份验证自定义命令
有两种身份验证方式:
使用 cy.origin()
登录
创建 loginByCognito
自定义命令来执行登录流程:
// 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)
})
测试中使用该命令:
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.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:
- Amplify v5.x.x
- Amplify v6.x.x
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('/')
})
import { Amplify } from 'aws-amplify'
import { fetchAuthSession, signIn } from 'aws-amplify/auth'
Amplify.configure(Cypress.env('awsConfig'))
const fetchJwts = async (username: string, password: string) => {
const options = { authFlowType: 'USER_PASSWORD_AUTH' as const }
await signIn({ username, password, options })
const authSession = await fetchAuthSession()
const tokens = authSession.tokens!
return {
idToken: tokens.idToken!.toString(),
accessToken: tokens.accessToken.toString(),
// 其他字段...
}
}
Cypress.Commands.add(
'loginByCognitoApi',
(username: string, password: string) => {
const log = Cypress.log({
displayName: 'COGNITO LOGIN',
message: [`🔐 Authenticating | ${username}`],
autoEnd: false,
})
cy.wrap(fetchJwts(username, password), { log: false }).then(
(unknownJwts) => {
// 处理 token...
}
)
}
)
测试中使用编程式登录:
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 应用进行测试
当使用 AWS AppSync 或 API Gateway 时,它们可直接验证 Cognito JWT。如果是自托管后端(如 Express),则需要适配。
真实世界应用(RWA) 提供了 React 前端和 Express 后端的配置和代码。
适配后端
安装 express-jwt 和 jwks-rsa 来验证 JWT:
- Amplify v5.x.x
- Amplify v6.x.x
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/*'],
})
const userPoolId = awsConfig.Auth.Cognito.userPoolId
const region = userPoolId.split('_')[0]
const awsCognitoJwtConfig = {
secret: jwksRsa.expressJwtSecret({
jwksUri: `https://cognito-idp.${region}.amazonaws.com/${userPoolId}/.well-known/jwks.json`,
}),
issuer: `https://cognito-idp.${region}.amazonaws.com/${userPoolId}`,
algorithms: ['RS256'],
}
全局应用中间件:
import { checkCognitoJwt } from './helpers'
if (process.env.REACT_APP_AWS_COGNITO) {
app.use(checkCognitoJwt)
}
适配前端
创建 AppCognito.tsx
组件处理 Cognito 身份验证:
- Amplify v6.x.x
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;
};
更新入口文件使用新组件:
if (process.env.REACT_APP_AWS_COGNITO) {
ReactDOM.render(
<Router history={history}>
<ThemeProvider theme={theme}>
<AppCognito />
</ThemeProvider>
</Router>,
document.getElementById('root')
)
}