From 49863532e16c592fefdae8518e434495e25cf24b Mon Sep 17 00:00:00 2001 From: donghyeon-ka Date: Sat, 25 Jul 2026 16:39:01 +0900 Subject: [PATCH] test: verify federation keeps the AP1 contract --- docs/federation-spa-zero-change.md | 16 +++++++ e2e/federation-zero-change.mjs | 46 ++++++++++++++++++++ e2e/package.json | 3 +- scripts/verify-federation-spa-zero-change.sh | 11 +++++ 4 files changed, 75 insertions(+), 1 deletion(-) create mode 100644 docs/federation-spa-zero-change.md create mode 100644 e2e/federation-zero-change.mjs create mode 100755 scripts/verify-federation-spa-zero-change.sh diff --git a/docs/federation-spa-zero-change.md b/docs/federation-spa-zero-change.md new file mode 100644 index 0000000..c8953b4 --- /dev/null +++ b/docs/federation-spa-zero-change.md @@ -0,0 +1,16 @@ +# Google federation without an AP1 application fork + +SPA는 Google SDK나 Google token endpoint를 알지 않는다. 기존 `spa-public` +client로 Keycloak authorization endpoint를 호출하고, Keycloak 로그인 +화면에서 mock Google을 선택해도 callback, PKCE 교환, access token audience, +Spring API 호출은 로컬 사용자 로그인과 동일하다. + +달라지는 곳은 Keycloak 앞단뿐이다. + +```text +SPA -> Keycloak -> Google/mock OIDC +SPA <- Keycloak access token <- Keycloak +``` + +`verify-federation-spa-zero-change.sh`는 기존 SPA 로그인 버튼에서 broker를 +선택하고, 변경 없는 callback과 `/api/me`가 200인지 실제 브라우저로 검증한다. diff --git a/e2e/federation-zero-change.mjs b/e2e/federation-zero-change.mjs new file mode 100644 index 0000000..f9f1be5 --- /dev/null +++ b/e2e/federation-zero-change.mjs @@ -0,0 +1,46 @@ +import assert from "node:assert/strict"; +import { chromium } from "playwright-core"; + +const password = process.env.MOCK_GOOGLE_USER_PASSWORD; +assert.ok(password); + +const browser = await chromium.launch({ + executablePath: process.env.CHROME_BIN ?? "/usr/bin/google-chrome", + headless: true, + args: ["--no-sandbox"], +}); + +try { + const page = await browser.newPage(); + const tokenResponse = page.waitForResponse((response) => + response.url().includes("/protocol/openid-connect/token") + && response.request().postData()?.includes("grant_type=authorization_code"), + ); + + await page.goto("http://localhost:8088/"); + await page.locator("#login").click(); + await page.locator('a[href*="/broker/mock-google/login"]').click(); + await page.waitForURL(/\/realms\/mock-google\//u); + await page.locator("#username").fill("mock-new-user"); + await page.locator("#password").fill(password); + await page.locator("#kc-login").click(); + + const response = await tokenResponse; + assert.equal(response.status(), 200); + const token = (await response.json()).access_token; + const payload = JSON.parse( + Buffer.from(token.split(".")[1], "base64url").toString(), + ); + assert.match(payload.preferred_username, /^mock-google\./u); + assert.ok(payload.aud.includes("keycloak-pattern-api")); + + await page.waitForURL("http://localhost:8088/"); + await page.locator('[data-authenticated="true"]').waitFor(); + await page.locator("#call-api").click(); + await page.waitForFunction(() => + document.querySelector("#result")?.textContent.includes('"httpStatus": 200'), + ); + console.log("Google federation verified with the unchanged AP1 SPA/API contract"); +} finally { + await browser.close(); +} diff --git a/e2e/package.json b/e2e/package.json index 3f4a3a9..8edd6cc 100644 --- a/e2e/package.json +++ b/e2e/package.json @@ -5,7 +5,8 @@ "type": "module", "scripts": { "test:pattern1": "node pattern1.mjs", - "test:role-mapping": "node role-mapping.mjs" + "test:role-mapping": "node role-mapping.mjs", + "test:federation-zero-change": "node federation-zero-change.mjs" }, "devDependencies": { "playwright-core": "1.62.0" diff --git a/scripts/verify-federation-spa-zero-change.sh b/scripts/verify-federation-spa-zero-change.sh new file mode 100755 index 0000000..5533677 --- /dev/null +++ b/scripts/verify-federation-spa-zero-change.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env sh +set -eu + +set -a +. ./.env +set +a + +./scripts/set-first-broker-login-mode.sh secure +npm --prefix e2e ci +MOCK_GOOGLE_USER_PASSWORD="$MOCK_GOOGLE_USER_PASSWORD" \ + npm --prefix e2e run test:federation-zero-change