From 14bda89d10ae021fa28db96c8585636bdaf36618 Mon Sep 17 00:00:00 2001 From: kyy Date: Wed, 6 May 2026 11:11:58 +0900 Subject: [PATCH] =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EC=88=98=ED=96=89=20?= =?UTF-8?q?=EB=A1=9C=EA=B7=B8=20=EC=B6=9C=EB=A0=A5=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app.js | 103 ++++++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 80 insertions(+), 23 deletions(-) diff --git a/app.js b/app.js index 9bfbc0c..ef61428 100644 --- a/app.js +++ b/app.js @@ -100,6 +100,12 @@ function removeSessionBinding(sessionId) { removeSessionBindingFromMap(sidToSessionIds, existing.sid, sessionId); removeSessionBindingFromMap(subToSessionIds, existing.sub, sessionId); sessionIdToBinding.delete(sessionId); + + console.log('[세션 매핑] 제거 완료', { + sessionId, + sid: existing.sid || '(없음)', + sub: existing.sub || '(없음)', + }); } function registerSessionBinding(sessionId, claims) { @@ -111,10 +117,12 @@ function registerSessionBinding(sessionId, claims) { addSessionBinding(sidToSessionIds, sid, sessionId); addSessionBinding(subToSessionIds, sub, sessionId); - console.log('[Session Binding] Registered', { + console.log('[세션 매핑] 등록 완료', { sessionId, - sid: sid || '(none)', - sub: sub || '(none)', + sid: sid || '(없음)', + sub: sub || '(없음)', + sidSessionCount: sid ? sidToSessionIds.get(sid)?.size || 0 : 0, + subSessionCount: sub ? subToSessionIds.get(sub)?.size || 0 : 0, }); } @@ -196,6 +204,7 @@ async function validateBaronSession(accessToken) { function destroyDemoSession(req, res) { const sessionId = req.sessionID; + console.log('[로컬 로그아웃] 현재 세션 정리 시작', { sessionId }); removeSessionBinding(sessionId); return new Promise((resolve) => { @@ -203,6 +212,7 @@ function destroyDemoSession(req, res) { if (res) { res.clearCookie('baron.demo.sid'); } + console.log('[로컬 로그아웃] 현재 세션 정리 완료', { sessionId }); resolve(); }); }); @@ -214,7 +224,7 @@ async function verifyBackchannelLogoutToken({ expectedAudience, jwks, }) { - const { payload } = await jwtVerify(logoutToken, jwks, { + const { payload, protectedHeader } = await jwtVerify(logoutToken, jwks, { issuer: expectedIssuer, audience: expectedAudience, }); @@ -245,6 +255,16 @@ async function verifyBackchannelLogoutToken({ throw new Error('logout_token replay detected'); } + console.log('[백채널 로그아웃] 토큰 검증 성공', { + alg: protectedHeader.alg, + kid: protectedHeader.kid || '(없음)', + iss: payload.iss, + aud: payload.aud, + sid: sid || '(없음)', + sub: sub || '(없음)', + jti, + }); + return { sid, sub, @@ -257,13 +277,20 @@ async function destroySessionsForLogout(store, claims) { const sessionIds = getSessionIdsForLogoutClaims(claims); let destroyedCount = 0; + console.log('[백채널 로그아웃] 세션 탐색 결과', { + sid: claims.sid || '(없음)', + sub: claims.sub || '(없음)', + matchedSessionIds: sessionIds, + }); + for (const sessionId of sessionIds) { removeSessionBinding(sessionId); try { await destroySessionById(store, sessionId); destroyedCount += 1; + console.log('[백채널 로그아웃] 세션 파기 완료', { sessionId }); } catch (error) { - console.error('[Backchannel Logout] Failed to destroy session', { + console.error('[백채널 로그아웃] 세션 파기 실패', { sessionId, error: error.message, }); @@ -280,10 +307,22 @@ async function setupOIDC() { const backchannelJwksUrl = deriveBackchannelJwksUrl(); const backchannelJwks = createRemoteJWKSet(new URL(backchannelJwksUrl)); - console.log(`Discovering issuer: ${issuerUrl}`); - console.log(`Back-channel logout JWKS: ${backchannelJwksUrl}`); + console.log(`OIDC Issuer 조회: ${issuerUrl}`); + console.log(`백채널 로그아웃 JWKS 주소: ${backchannelJwksUrl}`); const issuer = await discovery(new URL(issuerUrl), clientId); issuer.token_endpoint_auth_method = 'none'; + const authorizationEndpoint = + (typeof issuer.serverMetadata === 'function' + ? issuer.serverMetadata()?.authorization_endpoint + : undefined) || + issuer.authorization_server_metadata?.authorization_endpoint || + issuer.authorization_endpoint || + '(확인 불가)'; + console.log('[시스템] OIDC 설정 완료', { + clientId, + redirectUri, + authorizationEndpoint, + }); app.use(async (req, res, next) => { const skipPaths = new Set(['/login', '/callback', '/logout', '/backchannel-logout']); @@ -305,14 +344,14 @@ async function setupOIDC() { return next(); } - console.warn('[Session Validation] Baron session is no longer valid', { + console.warn('[세션 검증] Baron 세션이 유효하지 않아 로컬 세션을 정리합니다.', { path: req.path, reason: validation.reason, }); await destroyDemoSession(req, res); return res.redirect('/'); } catch (error) { - console.error('[Session Validation] Failed to validate Baron session', error); + console.error('[세션 검증] Baron 세션 확인 실패로 로컬 세션을 정리합니다.', error); await destroyDemoSession(req, res); return res.redirect('/'); } @@ -323,15 +362,15 @@ async function setupOIDC() { }); app.get('/login', async (req, res) => { - console.log(`\n[Login Start] Session: ${req.sessionID}`); + console.log(`\n[로그인 시작] 세션 ID: ${req.sessionID}`); if (!req.session.state) { req.session.code_verifier = randomPKCECodeVerifier(); req.session.state = randomState(); req.session.nonce = randomNonce(); - console.log(`[Login] New state generated: ${req.session.state}`); + console.log('[로그인] 신규 state/nonce/code_verifier 생성 완료'); } else { - console.log(`[Login] Re-using existing state: ${req.session.state}`); + console.log('[로그인] 기존 state 재사용'); } const code_challenge = await calculatePKCECodeChallenge(req.session.code_verifier); @@ -350,14 +389,17 @@ async function setupOIDC() { state: req.session.state, }); + console.log('[로그인] Baron 인증 화면으로 이동', { url: url.href }); res.redirect(url.href); }); }); app.get('/callback', async (req, res) => { - console.log(`\n[Callback Start] Session: ${req.sessionID}`); - console.log(`[Callback Info] State from URL: ${req.query.state}`); - console.log(`[Callback Info] State in Session: ${req.session.state}`); + console.log(`\n[콜백 시작] 세션 ID: ${req.sessionID}`); + console.log('[콜백] URL state / 세션 state', { + urlState: req.query.state, + sessionState: req.session.state, + }); if (!req.session.state || !req.session.code_verifier) { if (req.session.user) { @@ -382,7 +424,7 @@ async function setupOIDC() { }, ); - console.log('[Callback Success] Token exchanged'); + console.log('[콜백] Authorization Code -> Token 교환 성공'); const tokenClaims = tokenset.claims(); const userinfo = await fetchUserInfo( @@ -398,9 +440,15 @@ async function setupOIDC() { delete req.session.code_verifier; delete req.session.nonce; + console.log('[콜백] 사용자 세션 생성 완료', { + sessionId: req.sessionID, + sid: tokenClaims.sid || '(없음)', + sub: tokenClaims.sub || '(없음)', + }); + req.session.save(() => res.redirect('/profile')); } catch (err) { - console.error('[Callback Error]', err); + console.error('[콜백] 인증 처리 실패', err); res.status(500).render('error', { message: 'Authentication Failed', detail: err.message, @@ -413,7 +461,14 @@ async function setupOIDC() { ? req.body.logout_token.trim() : ''; + console.log('\n[백채널 로그아웃] 요청 수신', { + hasLogoutToken: logoutToken !== '', + contentType: req.headers['content-type'] || '(없음)', + userAgent: req.headers['user-agent'] || '(없음)', + }); + if (!logoutToken) { + console.warn('[백채널 로그아웃] logout_token 누락'); return res.status(400).json({ error: 'logout_token is required' }); } @@ -426,9 +481,9 @@ async function setupOIDC() { }); const result = await destroySessionsForLogout(sessionMiddleware.store, claims); - console.log('[Backchannel Logout] Processed', { - sid: claims.sid || '(none)', - sub: claims.sub || '(none)', + console.log('[백채널 로그아웃] 처리 완료', { + sid: claims.sid || '(없음)', + sub: claims.sub || '(없음)', destroyedCount: result.destroyedCount, sessionIds: result.sessionIds, }); @@ -438,7 +493,7 @@ async function setupOIDC() { destroyedSessionCount: result.destroyedCount, }); } catch (error) { - console.error('[Backchannel Logout] Verification failed', error); + console.error('[백채널 로그아웃] 검증 또는 세션 정리 실패', error); return res.status(400).json({ error: 'invalid logout token', detail: error.message, @@ -448,8 +503,10 @@ async function setupOIDC() { app.get('/profile', (req, res) => { if (!req.session.user) { + console.log('[프로필] 비로그인 상태로 접근하여 루트로 이동'); return res.redirect('/'); } + console.log('[프로필] 로그인 세션으로 접근', { sessionId: req.sessionID }); res.render('profile', { user: req.session.user }); }); @@ -460,11 +517,11 @@ async function setupOIDC() { }); app.listen(port, '0.0.0.0', () => { - console.log(`Demo app listening at http://localhost:${port}`); + console.log(`[시스템] 데모 앱이 실행 중입니다. http://localhost:${port}`); }); } setupOIDC().catch((err) => { - console.error('OIDC setup failed:', err); + console.error('[시스템] OIDC 초기화 실패', err); process.exit(1); });