Google身份验证
您将学习到
- 如何设置Cypress来测试Google身份验证
- 如何在Cypress中配置Google应用凭证
- 如何创建自定义Google身份验证命令
- 如何为测试适配Google应用
我们将使用的测试技术是通过 Google OAuth 2.0 Playground 创建一个刷新令牌,该令牌可以在测试阶段兑换为访问令牌和ID令牌。
Google项目和应用设置
首先需要一个Google项目。如果还没有项目,可以使用 Google Cloud Console创建一个。更多信息请参考 Google Cloud APIs入门指南。
接下来,使用Google API控制台
为您的Web应用创建凭证。
在顶部导航中点击创建凭证
并选择OAuth客户端ID
。
在创建OAuth客户端ID
页面,输入以下内容:
- 应用类型: Web应用
- 名称: 您的Web应用名称
- 授权JavaScript来源:
http://localhost:3000
- 授权重定向URI:
http://localhost:3000/callback
和https://developers.google.com/oauthplayground
保存后,记下客户端ID和客户端密钥。您可以在 Google API凭证 页面的"OAuth 2.0客户端ID"下找到这些信息。
使用Google OAuth 2.0 Playground创建测试凭证
此过程生成的刷新令牌与认证的Google用户唯一对应。 必须为每个测试用户重复此过程。
访问
Google OAuth 2.0 Playground。
点击右上角的图标显示
OAuth 2.0配置
面板。在此面板中设置以下内容:
- OAuth流程: 服务器端
- 访问类型: 离线
- 勾选
使用您自己的OAuth凭证
- OAuth客户端ID: 您的Google应用客户端ID
- OAuth客户端密钥: 您的Google应用客户端密钥
在步骤1(选择并授权API)下选择应用所需的Google API,
至少包括Google OAuth2 API v2下的
https://www.googleapis.com/auth/userinfo.profile
端点。点击授权API。
接下来,使用测试Google用户账号登录。您将被重定向回 Google OAuth 2.0 Playground 的步骤2(用授权码兑换令牌)。点击 用授权码兑换令牌按钮。
您将进入步骤3(配置API请求)。记下返回的 刷新令牌以供测试使用。
在Cypress中设置Google应用凭证
为了在测试中使用测试用户凭证,我们需要配置
Cypress使用.env
文件中设置的Google环境变量。
REACT_APP_GOOGLE_CLIENTID = 'your-client-id'
REACT_APP_GOOGLE_CLIENT_SECRET = 'your-client-secret'
GOOGLE_REFRESH_TOKEN = 'your-refresh-token'
- cypress.config.js 文件
- cypress.config.ts 文件
const { defineConfig } = require('cypress')
// 从.env文件填充process.env
require('dotenv').config()
module.exports = defineConfig({
env: {
googleRefreshToken: process.env.GOOGLE_REFRESH_TOKEN,
googleClientId: process.env.REACT_APP_GOOGLE_CLIENTID,
googleClientSecret: process.env.REACT_APP_GOOGLE_CLIENT_SECRET,
},
})
import { defineConfig } from 'cypress'
// 从.env文件填充process.env
require('dotenv').config()
export default defineConfig({
env: {
googleRefreshToken: process.env.GOOGLE_REFRESH_TOKEN,
googleClientId: process.env.REACT_APP_GOOGLE_CLIENTID,
googleClientSecret: process.env.REACT_APP_GOOGLE_CLIENT_SECRET,
},
})
Google身份验证的自定义命令
接下来,我们将编写一个名为loginByGoogleApi
的命令,以编程方式
登录Google并在localStorage
中设置一个包含
认证用户详情的项,我们将在应用代码中使用它来验证
测试中 的认证状态。
loginByGoogleApi
命令将执行以下步骤:
- 使用来自
Google OAuth 2.0 Playground
的刷新令牌执行编程登录,将刷新令牌兑换为
access_token
- 使用返回的
access_token
获取Google用户资料 - 最后将
googleCypress
localStorage项设置为包含访问令牌
和用户资料
Cypress.Commands.add('loginByGoogleApi', () => {
cy.log('登录Google')
cy.request({
method: 'POST',
url: 'https://www.googleapis.com/oauth2/v4/token',
body: {
grant_type: 'refresh_token',
client_id: Cypress.env('googleClientId'),
client_secret: Cypress.env('googleClientSecret'),
refresh_token: Cypress.env('googleRefreshToken'),
},
}).then(({ body }) => {
const { access_token, id_token } = body
cy.request({
method: 'GET',
url: 'https://www.googleapis.com/oauth2/v3/userinfo',
headers: { Authorization: `Bearer ${access_token}` },
}).then(({ body }) => {
cy.log(body)
const userItem = {
token: id_token,
user: {
googleId: body.sub,
email: body.email,
givenName: body.given_name,
familyName: body.family_name,
imageUrl: body.picture,
},
}
window.localStorage.setItem('googleCypress', JSON.stringify(userItem))
cy.visit('/')
})
})
})
正确设置Google应用、配置必要的环境变量
并实现loginByGoogleApi
命令后,我们就可以在测试应用时
通过Google进行认证。以下是通过
Google以用户身份登录、完成引导流程并退出的测试。
该测试的 可运行版本 在真实世界应用(RWA)中。
describe('Google', function () {
beforeEach(function () {
cy.task('db:seed')
cy.loginByGoogleApi()
})
it('显示引导页面', function () {
cy.contains('开始使用').should('be.visible')
})
})
为测试适配Google应用
前面的部分重点介绍了Cypress测试中推荐的Google身份验证实践。 要使用此实践,假设您正在测试一个适当构建或适配使用Google的应用。
以下部分提供了构建或适配应用以使用Google身份验证的指导。
真实世界应用(RWA)被 使用,并为React SPA和Express后端提供了配置和可运行代码。
前端使用 react-google-login组件, 后端使用express-jwt来 验证Google提供的JWT。
启动
Cypress Real World App时使用yarn dev:google
命令。
适配后端
为了验证来自前端的API请求,我们安装 express-jwt和 jwks-rsa并配置对 来自Google的JWT的验证。
import jwt from 'express-jwt'
import jwksRsa from 'jwks-rsa'
dotenv.config()
const googleJwtConfig = {
secret: jwksRsa.expressJwtSecret({
cache: true,
rateLimit: true,
jwksRequestsPerMinute: 5,
jwksUri: 'https://www.googleapis.com/oauth2/v3/certs',
}),
// 验证受众和颁发者
audience: process.env.REACT_APP_GOOGLE_CLIENTID,
issuer: 'accounts.google.com',
algorithms: ['RS256'],
}
接下来,我们将定义一个Express中间件函数,用于在路由中
验证前端API请求发送的Google JWT
作为Bearer
令牌。
// ...
export const checkJwt = jwt(googleJwtConfig).unless({ path: ['/testData/*'] })
定义此辅助函数后,我们可以全局应用它到所有路由:
// 初始导入 ...
import { checkJwt } from './helpers'
// ...
if (process.env.REACT_APP_GOOGLE) {
app.use(checkJwt)
}
// 路由 ...
适配前端
我们需要更新前端React应用以允许通过 Google进行认证。如上所述,前端使用 react-google-login组件 来执行登录。
首先,我们创建一个AppGoogle.tsx
容器来渲染通过Google认证的
应用。该组件与App.tsx
组件相同,但增加了
一个GoogleLogin
组件替代原来的注册和登录组件。
添加了一个useGoogleLogin
钩子来发送带有user
和
token
对象的GOOGLE
事件,与现有的认证层
(authMachine.ts
)一起工作。
完整的 AppGoogle.tsx组件 在真实世界应用(RWA)中。
// 初始导入 ...
import { GoogleLogin, useGoogleLogin } from 'react-google-login'
// ...
const AppGoogle = () => {
// ...
useGoogleLogin({
clientId: process.env.REACT_APP_GOOGLE_CLIENTID!,
onSuccess: (res) => {
authService.send('GOOGLE', { user: res.profileObj, token: res.tokenId })
},
cookiePolicy: 'single_host_origin',
isSignedIn: true,
})
// ...
const isLoggedIn =
isAuthenticated &&
(authState.matches('authorized') ||
authState.matches('refreshing') ||
authState.matches('updating'))
return (
<div className={classes.root}>
// ...
{authState.matches('unauthorized') && (
<Container component="main" maxWidth="xs">
<CssBaseline />
<div className={classes.paper}>
<GoogleLogin
clientId={process.env.REACT_APP_GOOGLE_CLIENTID!}
buttonText="登录"
cookiePolicy={'single_host_origin'}
/>
</div>
</Container>
)}
</div>
)
}
export default AppGoogle
接下来,我们更新入口点(index.tsx
)以有条件地加载
AppGoogle
组件,如果我们在启动应用时将REACT_APP_GOOGLE
环境变量设置为true
。
import React from 'react'
import ReactDOM from 'react-dom'
import { Router } from 'react-router-dom'
import { history } from './utils/historyUtils'
import App from './containers/App'
import AppGoogle from './containers/AppGoogle'
import { createMuiTheme, ThemeProvider } from '@material-ui/core'
const theme = createMuiTheme({
palette: {
secondary: {
main: '#fff',
},
},
})
ReactDOM.render(
<Router history={history}>
<ThemeProvider theme={theme}>
{process.env.REACT_APP_GOOGLE ? <AppGoogle /> : <App />}
</ThemeProvider>
</Router>,
document.getElementById('root')
)