跨主机环境下如何用Cypress实现Keycloak登录测试
解决方案:Cypress跨域测试Keycloak登录问题
针对你遇到的Angular14 + keycloak-angular + Keycloak21跨主机场景下,Cypress13无法识别登录跳转页面的问题,给出以下几个可行方案:
1. 修正cy.origin的使用方式
你当前的cy.origin代码可能存在时机或URL匹配问题,试试调整为以下流程:
// 先触发应用内的登录按钮,启动跳转流程 cy.get('[data-testid="login-button"]').click(); // 等待URL跳转到Keycloak域名,确保跳转完成后再进入origin上下文 cy.url().should('include', thirdPartyServerUrl); cy.origin(thirdPartyServerUrl, { args: { username, password } }, ({ username, password }) => { // 增加超时等待,确保Keycloak登录表单完全加载 cy.get('[id=username]', { timeout: 10000 }) .should('be.visible') .type(username); cy.get('[id=password]') .should('be.visible') .type(password); cy.get('#kc-login').click(); });
关键注意点:
- 确保
thirdPartyServerUrl完全匹配Keycloak的域名(包括协议http/https、端口号) - 开启Cypress配置中的
experimentalSessionAndOrigin: true(必须项,否则跨域上下文无法正常切换)
2. 禁用Keycloak的iframe检查
keycloak-angular默认会启用iframe登录状态检查,这会干扰Cypress的跨域处理。修改Angular的Keycloak初始化配置:
// app.module.ts import { KeycloakAngularModule, KeycloakService } from 'keycloak-angular'; export function initializeKeycloak(keycloak: KeycloakService) { return () => keycloak.init({ config: { url: 'https://your-keycloak-domain/auth', realm: 'your-realm', clientId: 'your-client-id', }, initOptions: { onLoad: 'login-required', checkLoginIframe: false, // 禁用iframe检查 responseMode: 'query', // 确保用重定向而非iframe处理回调 }, }); } @NgModule({ imports: [KeycloakAngularModule], providers: [ { provide: APP_INITIALIZER, useFactory: initializeKeycloak, multi: true, deps: [KeycloakService], }, ], }) export class AppModule {}
3. 绕过UI登录,直接通过API注入登录状态
如果跨域UI登录始终有问题,直接通过Keycloak API获取凭证并设置到浏览器存储中,模拟已登录状态:
步骤1:添加Cypress自定义命令
在cypress/support/commands.js中添加:
Cypress.Commands.add('loginViaKeycloakApi', (username, password) => { const keycloakUrl = Cypress.env('keycloak_url'); const realm = Cypress.env('keycloak_realm'); const clientId = Cypress.env('keycloak_client_id'); const redirectUri = Cypress.config('baseUrl'); // 生成PKCE所需的code_verifier和code_challenge(keycloak-angular默认使用PKCE) const codeVerifier = Cypress._.random(0, 1e32).toString(36); const codeChallenge = btoa(unescape(encodeURIComponent(codeVerifier))) .replace(/\+/g, '-') .replace(/\//g, '_') .replace(/=/g, ''); // 1. 获取授权码 cy.request({ method: 'POST', url: `${keycloakUrl}/realms/${realm}/protocol/openid-connect/auth`, form: true, followRedirect: false, // 禁止自动跳转,提取授权码 body: { client_id: clientId, response_type: 'code', scope: 'openid', redirect_uri: redirectUri, code_challenge: codeChallenge, code_challenge_method: 'S256', username, password, }, }).then((authResponse) => { const authCode = new URL(authResponse.headers.location).searchParams.get('code'); // 2. 用授权码交换token return cy.request({ method: 'POST', url: `${keycloakUrl}/realms/${realm}/protocol/openid-connect/token`, form: true, body: { client_id: clientId, grant_type: 'authorization_code', code: authCode, redirect_uri: redirectUri, code_verifier: codeVerifier, }, }); }).then((tokenResponse) => { const { id_token, access_token, refresh_token, expires_in } = tokenResponse.body; // 设置keycloak-angular存储的token到localStorage(键名需匹配库的实际存储键) localStorage.setItem('keycloak-token', JSON.stringify({ idToken: id_token, accessToken: access_token, refreshToken: refresh_token, expiresAt: Date.now() + (expires_in * 1000), })); // 可选:设置Keycloak的HttpOnly cookie(如果应用依赖cookie验证) const keycloakDomain = new URL(keycloakUrl).hostname; cy.setCookie('KEYCLOAK_IDENTITY', btoa(id_token), { domain: keycloakDomain, path: '/auth', secure: true, httpOnly: true, }); cy.setCookie('KEYCLOAK_SESSION', `${realm}/${btoa(id_token)}`, { domain: keycloakDomain, path: '/auth', secure: true, httpOnly: true, }); }); });
步骤2:在测试中使用该命令
beforeEach(() => { // 先清除旧的存储状态 cy.clearLocalStorage(); cy.clearCookies(); // 调用API登录 cy.loginViaKeycloakApi('your-test-username', 'your-test-password'); // 访问应用首页,此时已处于登录状态 cy.visit('/'); });
步骤3:更新Cypress配置
在cypress.config.js中确保以下配置:
const { defineConfig } = require('cypress'); module.exports = defineConfig({ e2e: { experimentalSessionAndOrigin: true, chromeWebSecurity: false, // 关闭Chrome跨域安全限制 allowedOrigins: ["https://your-keycloak-domain"], // 允许访问Keycloak域名 env: { keycloak_url: 'https://your-keycloak-domain/auth', keycloak_realm: 'your-realm', keycloak_client_id: 'your-client-id', }, }, });
内容的提问来源于stack exchange,提问作者bav
相关产品推荐
相关产品推荐

