You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

跨主机环境下如何用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.11 18:44:50