From 35d5d70a1d646daab85e2bfb91071dc22914432f Mon Sep 17 00:00:00 2001 From: giteaadm Date: Tue, 23 Jun 2026 09:29:58 +0900 Subject: [PATCH] =?UTF-8?q?=EB=AF=B8=EC=9D=B8=EC=A6=9D=20=EC=A7=84?= =?UTF-8?q?=EC=9E=85=20=EC=8B=9C=20=EC=A4=91=EA=B0=84=20=ED=99=94=EB=A9=B4?= =?UTF-8?q?=20=EC=97=86=EC=9D=B4=20=EA=B3=A7=EC=9E=A5=20Keycloak=20?= =?UTF-8?q?=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=ED=8F=BC=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EB=A6=AC=EB=8B=A4=EC=9D=B4=EB=A0=89=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - requireAuth: /login 거치지 않고 loginRedirect로 직접 Keycloak으로 - /login 라우트도 oidcReady면 즉시 리다이렉트 - OIDC discovery 실패 시에만 안내 페이지 폴백, /api/*는 401 유지 Co-Authored-By: Claude Opus 4.8 --- src/auth.js | 12 ++++++++++-- src/server.js | 9 +++++++-- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/src/auth.js b/src/auth.js index 8558cab..15a9231 100644 --- a/src/auth.js +++ b/src/auth.js @@ -4,6 +4,11 @@ import { config } from "./config.js"; let client; +// OIDC 클라이언트가 준비됐는지(discovery 성공) 여부 +export function oidcReady() { + return !!client; +} + export async function initOidc() { const issuer = await Issuer.discover(config.oidc.issuer); client = new issuer.Client({ @@ -47,7 +52,10 @@ export async function handleCallback(req) { // 로그인 필수 가드 export function requireAuth(req, res, next) { if (req.session.user) return next(); - // API 요청은 401, 페이지 요청은 로그인으로 + // API 요청은 401 if (req.path.startsWith("/api/")) return res.status(401).json({ error: "unauthorized" }); - return res.redirect("/login"); + // 페이지 요청은 중간 화면 없이 곧장 Keycloak 로그인 폼으로 보낸다. + if (oidcReady()) return loginRedirect(req, res); + // OIDC 미준비(discovery 실패)면 안내 페이지로 폴백 + return res.redirect("/login?error=" + encodeURIComponent("로그인 서비스를 사용할 수 없습니다. 잠시 후 다시 시도해 주세요.")); } diff --git a/src/server.js b/src/server.js index 393d2ca..afa8269 100644 --- a/src/server.js +++ b/src/server.js @@ -5,7 +5,7 @@ import { fileURLToPath } from "url"; import path from "path"; import { config } from "./config.js"; import { siteGroups } from "./sites.js"; -import { initOidc, loginRedirect, handleCallback, requireAuth } from "./auth.js"; +import { initOidc, loginRedirect, handleCallback, requireAuth, oidcReady } from "./auth.js"; import * as db from "./db.js"; const __dirname = path.dirname(fileURLToPath(import.meta.url)); @@ -36,9 +36,14 @@ app.use((req, res, next) => { app.get("/healthz", (_req, res) => res.json({ ok: true })); // --- 인증 --- +// SSO 세션이 없으면 중간 페이지 없이 바로 Keycloak(커스텀 테마) 로그인으로 보낸다. +// (OIDC 초기화 실패 등으로 로그인 불가하면 안내용 login.ejs 로 폴백) app.get("/login", (req, res) => { if (req.session.user) return res.redirect("/"); - res.render("login"); + if (req.query.error) return res.render("login", { error: req.query.error }); + // OIDC 준비됐으면 중간 화면 없이 곧장 Keycloak 로그인 폼으로 + if (oidcReady()) return loginRedirect(req, res); + return res.render("login", { error: "로그인 서비스를 사용할 수 없습니다. 잠시 후 다시 시도해 주세요." }); }); app.get("/auth/login", (req, res) => loginRedirect(req, res)); app.get("/auth/callback", async (req, res) => {