로직 수행 로그 출력화

This commit is contained in:
2026-05-06 11:11:58 +09:00
parent ca9ff7ba25
commit 14bda89d10

103
app.js
View File

@@ -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);
});