From ebe273ef4564d55f9bf193adc51a9e58211e72e9 Mon Sep 17 00:00:00 2001 From: joonhoekim <26rote@gmail.com> Date: Mon, 23 Jun 2025 06:44:34 +0000 Subject: (김준회 SAML 2.0 SSO 리팩터링, 디버깅 유틸리티 추가, MOCK 처리 추가 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/api/auth/saml/authn-request/route.ts | 76 ++++++++++++----- app/api/auth/saml/mock-idp/route.ts | 137 +++++++++++++++++++++++++++++++ 2 files changed, 194 insertions(+), 19 deletions(-) create mode 100644 app/api/auth/saml/mock-idp/route.ts (limited to 'app/api/auth/saml') diff --git a/app/api/auth/saml/authn-request/route.ts b/app/api/auth/saml/authn-request/route.ts index e3cb8a47..f079aea0 100644 --- a/app/api/auth/saml/authn-request/route.ts +++ b/app/api/auth/saml/authn-request/route.ts @@ -1,45 +1,83 @@ -import { NextRequest, NextResponse } from 'next/server' +/** + * SAML 2.0 SSO AuthnRequest 생성 API + * + * 역할: + * - 프론트엔드에서 SAML 로그인 URL을 요청할 때 사용 + * - SAML AuthnRequest를 생성하고 IdP 로그인 URL 반환 + * - Mock 모드 지원으로 개발/테스트 환경에서 시뮬레이션 가능 + * + * 플로우: + * 1. 사용자가 "Knox SSO로 로그인" 버튼 클릭 + * 2. 프론트엔드에서 이 API 호출 + * 3. SAML AuthnRequest URL 생성 후 반환 + * 4. 프론트엔드에서 해당 URL로 리다이렉트 + * 5. IdP에서 인증 후 /api/saml/callback으로 SAML Response 전송 + */ + +import { NextResponse } from 'next/server' import { createAuthnRequest } from '../../[...nextauth]/saml/utils' +import { debugLog, debugError, debugSuccess, debugProcess } from '@/lib/debug-utils' -const samlEnvironment = { - NODE_ENV: process.env.NODE_ENV, - SAML_USE_MOCKUP: process.env.SAML_USE_MOCKUP, - NEXTAUTH_URL: process.env.NEXTAUTH_URL, -} +// SAML 환경변수 상태 체크 +function validateSAMLEnvironment() { + const samlEnvironment = { + NODE_ENV: process.env.NODE_ENV, + SAML_MOCKING_IDP: process.env.SAML_MOCKING_IDP, + NEXTAUTH_URL: process.env.NEXTAUTH_URL, + SAML_SP_PRIVATE_KEY: process.env.SAML_SP_PRIVATE_KEY ? '✅ Set' : '❌ Missing', + SAML_SP_CERT: process.env.SAML_SP_CERT ? '✅ Set' : '❌ Missing', + } + + debugLog('📊 SAML Environment check:', JSON.stringify(samlEnvironment, null, 2)) + + // 필수 환경변수 검증 + const missingVars = [] + if (!process.env.NEXTAUTH_URL) missingVars.push('NEXTAUTH_URL') -// 환경변수 체크 -function checkEnvironment() { - console.log('📊 Environment check:', JSON.stringify(samlEnvironment, null, 2)) + // 키 없어도 구현 가능해서 주석 처리함. + // if (!process.env.SAML_SP_PRIVATE_KEY) missingVars.push('SAML_SP_PRIVATE_KEY') + // if (!process.env.SAML_SP_CERT) missingVars.push('SAML_SP_CERT') + if (missingVars.length > 0) { + throw new Error(`Missing required SAML environment variables: ${missingVars.join(', ')}`) + } + + return samlEnvironment } -// 요청 받으면 따로 파싱할 것 없이 동일하게 행동하므로 아규먼트 없음 +/** + * SAML AuthnRequest URL 생성 엔드포인트 + * + * @returns {JSON} { loginUrl: string, success: boolean, isThisMocking?: boolean } + */ export async function GET() { - console.log('🚀 SAML AuthnRequest API started') - checkEnvironment() - + debugProcess('🚀 SAML AuthnRequest API started') + try { - console.log('SSO STEP 1: Create AuthnRequest') + // 환경변수 검증 + const environment = validateSAMLEnvironment() + + debugProcess('SSO STEP 1: Create AuthnRequest') const startTime = Date.now() const loginUrl = await createAuthnRequest() const endTime = Date.now() - console.log('SAML AuthnRequest created successfully:', { - url: loginUrl, + debugSuccess('SAML AuthnRequest created successfully:', { + url: loginUrl.substring(0, 100) + '...', urlLength: loginUrl.length, processingTime: `${endTime - startTime}ms`, + mockMode: environment.SAML_MOCKING_IDP === 'true', timestamp: new Date().toISOString() }) return NextResponse.json({ loginUrl, success: true, - mode: 'real', - message: 'Using real SAML IdP' + isThisMocking: environment.SAML_MOCKING_IDP === 'true' }) } catch (error) { - console.error('Failed to create SAML AuthnRequest:', { + debugError('Failed to create SAML AuthnRequest:', { error: error instanceof Error ? error.message : 'Unknown error', stack: error instanceof Error ? error.stack : undefined, timestamp: new Date().toISOString() diff --git a/app/api/auth/saml/mock-idp/route.ts b/app/api/auth/saml/mock-idp/route.ts new file mode 100644 index 00000000..45c670b0 --- /dev/null +++ b/app/api/auth/saml/mock-idp/route.ts @@ -0,0 +1,137 @@ +import { NextRequest, NextResponse } from 'next/server' + +// Mock IdP 엔드포인트 - SAML Response HTML 폼 반환 +export async function GET(request: NextRequest) { + try { + console.log('🎭 Mock IdP endpoint accessed'); + + // Mock SAML Response 데이터 (실제 형태와 일치하도록 문자열 형태) + const mockSAMLResponseData = { + nameID: "testuser@samsung.com", + nameIDFormat: "urn:oasis:names:tc:SAML:2.0:nameid-format:emailAddress", + attributes: { + email: "testuser@samsung.com", + name: "홍길동", + } + }; + + // Mock XML SAML Response 생성 + const mockXML = ` + + MockIdP + + + + + MockIdP + + ${mockSAMLResponseData.nameID} + + + + ${mockSAMLResponseData.attributes.email} + + + ${mockSAMLResponseData.attributes.name} + + + + + `.trim(); + + // Base64 인코딩 + const encodedSAMLResponse = Buffer.from(mockXML, 'utf-8').toString('base64'); + + console.log("🎭 Mock SAML Response created:", { + nameID: mockSAMLResponseData.nameID, + email: mockSAMLResponseData.attributes.email, + name: mockSAMLResponseData.attributes.name, + encodedLength: encodedSAMLResponse.length + }); + + // 콜백 URL로 POST 요청을 시뮬레이션하는 HTML 폼 반환 + const callbackUrl = `${process.env.NEXTAUTH_URL}/api/saml/callback`; // process.env.SAML_SP_CALLBACK_URL + + const mockFormHTML = ` + + + + Mock SAML IdP + + + +
+

🎭 Mock SAML IdP

+
+ 테스트 모드: 실제 IdP 대신 Mock 응답을 사용합니다.
+ 실제 데이터 형태와 일치하도록 attributes를 문자열로 전송합니다. +
+
+ + + +
+
+

테스트 사용자 정보:

+ +

5초 후 자동으로 로그인을 진행합니다...

+

프로덕션 환경에서는 SAML_MOCKING_IDP=false로 설정하세요.

+
+
+ + + + `; + + return new NextResponse(mockFormHTML, { + headers: { + 'Content-Type': 'text/html; charset=utf-8', + 'Cache-Control': 'no-cache, no-store, must-revalidate' + } + }); + + } catch (error) { + console.error('💥 Mock IdP error:', error); + return NextResponse.json({ + error: 'Mock IdP failed', + details: error instanceof Error ? error.message : 'Unknown error' + }, { status: 500 }); + } +} \ No newline at end of file -- cgit v1.2.3