로직 수행 로그 출력화
This commit is contained in:
103
app.js
103
app.js
@@ -100,6 +100,12 @@ function removeSessionBinding(sessionId) {
|
|||||||
removeSessionBindingFromMap(sidToSessionIds, existing.sid, sessionId);
|
removeSessionBindingFromMap(sidToSessionIds, existing.sid, sessionId);
|
||||||
removeSessionBindingFromMap(subToSessionIds, existing.sub, sessionId);
|
removeSessionBindingFromMap(subToSessionIds, existing.sub, sessionId);
|
||||||
sessionIdToBinding.delete(sessionId);
|
sessionIdToBinding.delete(sessionId);
|
||||||
|
|
||||||
|
console.log('[세션 매핑] 제거 완료', {
|
||||||
|
sessionId,
|
||||||
|
sid: existing.sid || '(없음)',
|
||||||
|
sub: existing.sub || '(없음)',
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function registerSessionBinding(sessionId, claims) {
|
function registerSessionBinding(sessionId, claims) {
|
||||||
@@ -111,10 +117,12 @@ function registerSessionBinding(sessionId, claims) {
|
|||||||
addSessionBinding(sidToSessionIds, sid, sessionId);
|
addSessionBinding(sidToSessionIds, sid, sessionId);
|
||||||
addSessionBinding(subToSessionIds, sub, sessionId);
|
addSessionBinding(subToSessionIds, sub, sessionId);
|
||||||
|
|
||||||
console.log('[Session Binding] Registered', {
|
console.log('[세션 매핑] 등록 완료', {
|
||||||
sessionId,
|
sessionId,
|
||||||
sid: sid || '(none)',
|
sid: sid || '(없음)',
|
||||||
sub: sub || '(none)',
|
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) {
|
function destroyDemoSession(req, res) {
|
||||||
const sessionId = req.sessionID;
|
const sessionId = req.sessionID;
|
||||||
|
console.log('[로컬 로그아웃] 현재 세션 정리 시작', { sessionId });
|
||||||
removeSessionBinding(sessionId);
|
removeSessionBinding(sessionId);
|
||||||
|
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
@@ -203,6 +212,7 @@ function destroyDemoSession(req, res) {
|
|||||||
if (res) {
|
if (res) {
|
||||||
res.clearCookie('baron.demo.sid');
|
res.clearCookie('baron.demo.sid');
|
||||||
}
|
}
|
||||||
|
console.log('[로컬 로그아웃] 현재 세션 정리 완료', { sessionId });
|
||||||
resolve();
|
resolve();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -214,7 +224,7 @@ async function verifyBackchannelLogoutToken({
|
|||||||
expectedAudience,
|
expectedAudience,
|
||||||
jwks,
|
jwks,
|
||||||
}) {
|
}) {
|
||||||
const { payload } = await jwtVerify(logoutToken, jwks, {
|
const { payload, protectedHeader } = await jwtVerify(logoutToken, jwks, {
|
||||||
issuer: expectedIssuer,
|
issuer: expectedIssuer,
|
||||||
audience: expectedAudience,
|
audience: expectedAudience,
|
||||||
});
|
});
|
||||||
@@ -245,6 +255,16 @@ async function verifyBackchannelLogoutToken({
|
|||||||
throw new Error('logout_token replay detected');
|
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 {
|
return {
|
||||||
sid,
|
sid,
|
||||||
sub,
|
sub,
|
||||||
@@ -257,13 +277,20 @@ async function destroySessionsForLogout(store, claims) {
|
|||||||
const sessionIds = getSessionIdsForLogoutClaims(claims);
|
const sessionIds = getSessionIdsForLogoutClaims(claims);
|
||||||
let destroyedCount = 0;
|
let destroyedCount = 0;
|
||||||
|
|
||||||
|
console.log('[백채널 로그아웃] 세션 탐색 결과', {
|
||||||
|
sid: claims.sid || '(없음)',
|
||||||
|
sub: claims.sub || '(없음)',
|
||||||
|
matchedSessionIds: sessionIds,
|
||||||
|
});
|
||||||
|
|
||||||
for (const sessionId of sessionIds) {
|
for (const sessionId of sessionIds) {
|
||||||
removeSessionBinding(sessionId);
|
removeSessionBinding(sessionId);
|
||||||
try {
|
try {
|
||||||
await destroySessionById(store, sessionId);
|
await destroySessionById(store, sessionId);
|
||||||
destroyedCount += 1;
|
destroyedCount += 1;
|
||||||
|
console.log('[백채널 로그아웃] 세션 파기 완료', { sessionId });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[Backchannel Logout] Failed to destroy session', {
|
console.error('[백채널 로그아웃] 세션 파기 실패', {
|
||||||
sessionId,
|
sessionId,
|
||||||
error: error.message,
|
error: error.message,
|
||||||
});
|
});
|
||||||
@@ -280,10 +307,22 @@ async function setupOIDC() {
|
|||||||
const backchannelJwksUrl = deriveBackchannelJwksUrl();
|
const backchannelJwksUrl = deriveBackchannelJwksUrl();
|
||||||
const backchannelJwks = createRemoteJWKSet(new URL(backchannelJwksUrl));
|
const backchannelJwks = createRemoteJWKSet(new URL(backchannelJwksUrl));
|
||||||
|
|
||||||
console.log(`Discovering issuer: ${issuerUrl}`);
|
console.log(`OIDC Issuer 조회: ${issuerUrl}`);
|
||||||
console.log(`Back-channel logout JWKS: ${backchannelJwksUrl}`);
|
console.log(`백채널 로그아웃 JWKS 주소: ${backchannelJwksUrl}`);
|
||||||
const issuer = await discovery(new URL(issuerUrl), clientId);
|
const issuer = await discovery(new URL(issuerUrl), clientId);
|
||||||
issuer.token_endpoint_auth_method = 'none';
|
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) => {
|
app.use(async (req, res, next) => {
|
||||||
const skipPaths = new Set(['/login', '/callback', '/logout', '/backchannel-logout']);
|
const skipPaths = new Set(['/login', '/callback', '/logout', '/backchannel-logout']);
|
||||||
@@ -305,14 +344,14 @@ async function setupOIDC() {
|
|||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
|
|
||||||
console.warn('[Session Validation] Baron session is no longer valid', {
|
console.warn('[세션 검증] Baron 세션이 유효하지 않아 로컬 세션을 정리합니다.', {
|
||||||
path: req.path,
|
path: req.path,
|
||||||
reason: validation.reason,
|
reason: validation.reason,
|
||||||
});
|
});
|
||||||
await destroyDemoSession(req, res);
|
await destroyDemoSession(req, res);
|
||||||
return res.redirect('/');
|
return res.redirect('/');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[Session Validation] Failed to validate Baron session', error);
|
console.error('[세션 검증] Baron 세션 확인 실패로 로컬 세션을 정리합니다.', error);
|
||||||
await destroyDemoSession(req, res);
|
await destroyDemoSession(req, res);
|
||||||
return res.redirect('/');
|
return res.redirect('/');
|
||||||
}
|
}
|
||||||
@@ -323,15 +362,15 @@ async function setupOIDC() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
app.get('/login', async (req, res) => {
|
app.get('/login', async (req, res) => {
|
||||||
console.log(`\n[Login Start] Session: ${req.sessionID}`);
|
console.log(`\n[로그인 시작] 세션 ID: ${req.sessionID}`);
|
||||||
|
|
||||||
if (!req.session.state) {
|
if (!req.session.state) {
|
||||||
req.session.code_verifier = randomPKCECodeVerifier();
|
req.session.code_verifier = randomPKCECodeVerifier();
|
||||||
req.session.state = randomState();
|
req.session.state = randomState();
|
||||||
req.session.nonce = randomNonce();
|
req.session.nonce = randomNonce();
|
||||||
console.log(`[Login] New state generated: ${req.session.state}`);
|
console.log('[로그인] 신규 state/nonce/code_verifier 생성 완료');
|
||||||
} else {
|
} else {
|
||||||
console.log(`[Login] Re-using existing state: ${req.session.state}`);
|
console.log('[로그인] 기존 state 재사용');
|
||||||
}
|
}
|
||||||
|
|
||||||
const code_challenge = await calculatePKCECodeChallenge(req.session.code_verifier);
|
const code_challenge = await calculatePKCECodeChallenge(req.session.code_verifier);
|
||||||
@@ -350,14 +389,17 @@ async function setupOIDC() {
|
|||||||
state: req.session.state,
|
state: req.session.state,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
console.log('[로그인] Baron 인증 화면으로 이동', { url: url.href });
|
||||||
res.redirect(url.href);
|
res.redirect(url.href);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
app.get('/callback', async (req, res) => {
|
app.get('/callback', async (req, res) => {
|
||||||
console.log(`\n[Callback Start] Session: ${req.sessionID}`);
|
console.log(`\n[콜백 시작] 세션 ID: ${req.sessionID}`);
|
||||||
console.log(`[Callback Info] State from URL: ${req.query.state}`);
|
console.log('[콜백] URL state / 세션 state', {
|
||||||
console.log(`[Callback Info] State in Session: ${req.session.state}`);
|
urlState: req.query.state,
|
||||||
|
sessionState: req.session.state,
|
||||||
|
});
|
||||||
|
|
||||||
if (!req.session.state || !req.session.code_verifier) {
|
if (!req.session.state || !req.session.code_verifier) {
|
||||||
if (req.session.user) {
|
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 tokenClaims = tokenset.claims();
|
||||||
const userinfo = await fetchUserInfo(
|
const userinfo = await fetchUserInfo(
|
||||||
@@ -398,9 +440,15 @@ async function setupOIDC() {
|
|||||||
delete req.session.code_verifier;
|
delete req.session.code_verifier;
|
||||||
delete req.session.nonce;
|
delete req.session.nonce;
|
||||||
|
|
||||||
|
console.log('[콜백] 사용자 세션 생성 완료', {
|
||||||
|
sessionId: req.sessionID,
|
||||||
|
sid: tokenClaims.sid || '(없음)',
|
||||||
|
sub: tokenClaims.sub || '(없음)',
|
||||||
|
});
|
||||||
|
|
||||||
req.session.save(() => res.redirect('/profile'));
|
req.session.save(() => res.redirect('/profile'));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('[Callback Error]', err);
|
console.error('[콜백] 인증 처리 실패', err);
|
||||||
res.status(500).render('error', {
|
res.status(500).render('error', {
|
||||||
message: 'Authentication Failed',
|
message: 'Authentication Failed',
|
||||||
detail: err.message,
|
detail: err.message,
|
||||||
@@ -413,7 +461,14 @@ async function setupOIDC() {
|
|||||||
? req.body.logout_token.trim()
|
? req.body.logout_token.trim()
|
||||||
: '';
|
: '';
|
||||||
|
|
||||||
|
console.log('\n[백채널 로그아웃] 요청 수신', {
|
||||||
|
hasLogoutToken: logoutToken !== '',
|
||||||
|
contentType: req.headers['content-type'] || '(없음)',
|
||||||
|
userAgent: req.headers['user-agent'] || '(없음)',
|
||||||
|
});
|
||||||
|
|
||||||
if (!logoutToken) {
|
if (!logoutToken) {
|
||||||
|
console.warn('[백채널 로그아웃] logout_token 누락');
|
||||||
return res.status(400).json({ error: 'logout_token is required' });
|
return res.status(400).json({ error: 'logout_token is required' });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -426,9 +481,9 @@ async function setupOIDC() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const result = await destroySessionsForLogout(sessionMiddleware.store, claims);
|
const result = await destroySessionsForLogout(sessionMiddleware.store, claims);
|
||||||
console.log('[Backchannel Logout] Processed', {
|
console.log('[백채널 로그아웃] 처리 완료', {
|
||||||
sid: claims.sid || '(none)',
|
sid: claims.sid || '(없음)',
|
||||||
sub: claims.sub || '(none)',
|
sub: claims.sub || '(없음)',
|
||||||
destroyedCount: result.destroyedCount,
|
destroyedCount: result.destroyedCount,
|
||||||
sessionIds: result.sessionIds,
|
sessionIds: result.sessionIds,
|
||||||
});
|
});
|
||||||
@@ -438,7 +493,7 @@ async function setupOIDC() {
|
|||||||
destroyedSessionCount: result.destroyedCount,
|
destroyedSessionCount: result.destroyedCount,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[Backchannel Logout] Verification failed', error);
|
console.error('[백채널 로그아웃] 검증 또는 세션 정리 실패', error);
|
||||||
return res.status(400).json({
|
return res.status(400).json({
|
||||||
error: 'invalid logout token',
|
error: 'invalid logout token',
|
||||||
detail: error.message,
|
detail: error.message,
|
||||||
@@ -448,8 +503,10 @@ async function setupOIDC() {
|
|||||||
|
|
||||||
app.get('/profile', (req, res) => {
|
app.get('/profile', (req, res) => {
|
||||||
if (!req.session.user) {
|
if (!req.session.user) {
|
||||||
|
console.log('[프로필] 비로그인 상태로 접근하여 루트로 이동');
|
||||||
return res.redirect('/');
|
return res.redirect('/');
|
||||||
}
|
}
|
||||||
|
console.log('[프로필] 로그인 세션으로 접근', { sessionId: req.sessionID });
|
||||||
res.render('profile', { user: req.session.user });
|
res.render('profile', { user: req.session.user });
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -460,11 +517,11 @@ async function setupOIDC() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
app.listen(port, '0.0.0.0', () => {
|
app.listen(port, '0.0.0.0', () => {
|
||||||
console.log(`Demo app listening at http://localhost:${port}`);
|
console.log(`[시스템] 데모 앱이 실행 중입니다. http://localhost:${port}`);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
setupOIDC().catch((err) => {
|
setupOIDC().catch((err) => {
|
||||||
console.error('OIDC setup failed:', err);
|
console.error('[시스템] OIDC 초기화 실패', err);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user