diff --git a/docs/three-leg-trust-chain.md b/docs/three-leg-trust-chain.md new file mode 100644 index 0000000..634ce76 --- /dev/null +++ b/docs/three-leg-trust-chain.md @@ -0,0 +1,15 @@ +# Three-leg trust chain + +| Hop | 발급자/행위자 | 검증자 | 핵심 검증 | +|---|---|---|---| +| 1 | Google/mock IdP | Keycloak broker | signature, upstream issuer, client audience, expiry, nonce | +| 2 | Keycloak | SPA | state, PKCE code binding, Keycloak issuer, token response | +| 3 | Keycloak access token | Spring Resource Server | Keycloak signature/JWKS, issuer, expiry, API audience, roles | + +Keycloak은 upstream ID token을 그대로 SPA에 전달하지 않는다. upstream +identity를 검증·연결한 뒤 자신의 realm key로 새 token을 발급한다. 그 결과 +backend의 trust store와 issuer 설정은 Google 로그인 여부와 무관하게 하나다. + +`verify-three-leg-trust-chain.sh`는 두 realm의 discovery issuer가 서로 다르고, +Spring 코드가 main Keycloak issuer만 신뢰하는지 확인한다. 실제 hop 전체는 +`verify-federation-spa-zero-change.sh`로 검증한다. diff --git a/scripts/verify-three-leg-trust-chain.sh b/scripts/verify-three-leg-trust-chain.sh new file mode 100755 index 0000000..d909b08 --- /dev/null +++ b/scripts/verify-three-leg-trust-chain.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env sh +set -eu + +base="${KEYCLOAK_URL:-http://localhost:8080}" +main_issuer="$base/realms/keycloak-patterns" +mock_issuer="$base/realms/mock-google" + +test "$(curl -fsS "$main_issuer/.well-known/openid-configuration" | jq -r .issuer)" \ + = "$main_issuer" +test "$(curl -fsS "$mock_issuer/.well-known/openid-configuration" | jq -r .issuer)" \ + = "$mock_issuer" + +rg -Fq 'SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_ISSUER_URI:' docker-compose.yml +if rg -qi 'accounts\\.google\\.com|realms/mock-google' backend/src/main; then + echo "backend must trust only the Keycloak issuer, not the upstream IdP" >&2 + exit 1 +fi + +echo "Three-leg trust anchors verified: upstream -> Keycloak -> Resource Server"