summaryrefslogtreecommitdiff
path: root/app/api
diff options
context:
space:
mode:
Diffstat (limited to 'app/api')
-rw-r--r--app/api/auth/[...nextauth]/saml/utils.ts19
-rw-r--r--app/api/auth/saml/authn-request/route.ts10
-rw-r--r--app/api/auth/saml/mock-idp/route.ts8
-rw-r--r--app/api/saml/callback/route.ts61
4 files changed, 66 insertions, 32 deletions
diff --git a/app/api/auth/[...nextauth]/saml/utils.ts b/app/api/auth/[...nextauth]/saml/utils.ts
index 73c00bf6..a5bcfe7a 100644
--- a/app/api/auth/[...nextauth]/saml/utils.ts
+++ b/app/api/auth/[...nextauth]/saml/utils.ts
@@ -97,15 +97,15 @@ export function createSAMLConfig() {
}
// SAML AuthnRequest 생성 (서버 액션)
-export async function createAuthnRequest(): Promise<string> {
+export async function createAuthnRequest(relayState?: string): Promise<string> {
"use server";
- console.log("SSO STEP 2: Create AuthnRequest");
+ console.log("SSO STEP 2: Create AuthnRequest", { relayState });
// Mock IdP 모드 체크
if (process.env.SAML_MOCKING_IDP === 'true') {
debugMock("Mock IdP mode enabled - simulating SAML response");
- return createMockSAMLFlow();
+ return createMockSAMLFlow(relayState);
}
try {
@@ -117,7 +117,7 @@ export async function createAuthnRequest(): Promise<string> {
const startTime = Date.now();
const authorizeUrl = await saml.getAuthorizeUrlAsync(
- "", // RelayState
+ relayState || "", // RelayState - 원래 가려던 페이지
undefined, // host
{
additionalParams: {},
@@ -406,12 +406,17 @@ export function mapSAMLProfileToUser(profile: SAMLProfile): SAMLUser {
}
// Mock SAML 플로우 생성 (테스트용)
-function createMockSAMLFlow(): string {
- debugMock("Creating mock SAML flow...");
+function createMockSAMLFlow(relayState?: string): string {
+ debugMock("Creating mock SAML flow...", { relayState });
// Mock 모드에서는 Mock IdP 엔드포인트로 리다이렉션
const baseUrl = process.env.NEXTAUTH_URL || 'http://localhost:3000';
- const mockIdpUrl = `${baseUrl}/api/auth/saml/mock-idp`;
+ let mockIdpUrl = `${baseUrl}/api/auth/saml/mock-idp`;
+
+ // RelayState가 있으면 URL 파라미터로 전달
+ if (relayState) {
+ mockIdpUrl += `?RelayState=${encodeURIComponent(relayState)}`;
+ }
debugMock("Mock SAML Flow - redirecting to Mock IdP:", mockIdpUrl);
diff --git a/app/api/auth/saml/authn-request/route.ts b/app/api/auth/saml/authn-request/route.ts
index f079aea0..6544a765 100644
--- a/app/api/auth/saml/authn-request/route.ts
+++ b/app/api/auth/saml/authn-request/route.ts
@@ -50,17 +50,23 @@ function validateSAMLEnvironment() {
*
* @returns {JSON} { loginUrl: string, success: boolean, isThisMocking?: boolean }
*/
-export async function GET() {
+export async function GET(request: Request) {
debugProcess('🚀 SAML AuthnRequest API started')
try {
+ // URL에서 RelayState 매개변수 추출
+ const url = new URL(request.url)
+ const relayState = url.searchParams.get('relayState')
+
+ debugLog('RelayState parameter:', relayState)
+
// 환경변수 검증
const environment = validateSAMLEnvironment()
debugProcess('SSO STEP 1: Create AuthnRequest')
const startTime = Date.now()
- const loginUrl = await createAuthnRequest()
+ const loginUrl = await createAuthnRequest(relayState || undefined)
const endTime = Date.now()
debugSuccess('SAML AuthnRequest created successfully:', {
diff --git a/app/api/auth/saml/mock-idp/route.ts b/app/api/auth/saml/mock-idp/route.ts
index 45c670b0..eccb6035 100644
--- a/app/api/auth/saml/mock-idp/route.ts
+++ b/app/api/auth/saml/mock-idp/route.ts
@@ -3,7 +3,11 @@ import { NextRequest, NextResponse } from 'next/server'
// Mock IdP 엔드포인트 - SAML Response HTML 폼 반환
export async function GET(request: NextRequest) {
try {
- console.log('🎭 Mock IdP endpoint accessed');
+ // RelayState 파라미터 추출
+ const url = new URL(request.url)
+ const relayState = url.searchParams.get('RelayState') || 'mock_test'
+
+ console.log('🎭 Mock IdP endpoint accessed', { relayState });
// Mock SAML Response 데이터 (실제 형태와 일치하도록 문자열 형태)
const mockSAMLResponseData = {
@@ -83,7 +87,7 @@ export async function GET(request: NextRequest) {
</div>
<form id="mockForm" method="POST" action="${callbackUrl}">
<input type="hidden" name="SAMLResponse" value="${encodedSAMLResponse}" />
- <input type="hidden" name="RelayState" value="mock_test" />
+ <input type="hidden" name="RelayState" value="${relayState}" />
<button type="submit" class="button">Continue with Mock Login</button>
</form>
<div class="details">
diff --git a/app/api/saml/callback/route.ts b/app/api/saml/callback/route.ts
index cf9ea772..7f454cb9 100644
--- a/app/api/saml/callback/route.ts
+++ b/app/api/saml/callback/route.ts
@@ -12,18 +12,16 @@ import { debugLog, debugError, debugSuccess, debugProcess } from '@/lib/debug-ut
// GET 요청시 SP 메타데이터를 반환해주는데, 이건 필요 없으면 지우면 된다.
export async function POST(request: NextRequest) {
+ // 안전한 baseUrl - 모든 리다이렉트에서 사용
+ const baseUrl = process.env.NEXTAUTH_URL || 'http://localhost:3000';
+
try {
const isMockMode = process.env.SAML_MOCKING_IDP === 'true';
debugProcess(`SAML Callback received at /api/saml/callback ${isMockMode ? '(🎭 Mock Mode)' : ''}`)
debugLog('Request info:', {
- url: request.url,
nextUrl: request.nextUrl?.toString(),
mockMode: isMockMode,
- headers: {
- host: request.headers.get('host'),
- origin: request.headers.get('origin'),
- referer: request.headers.get('referer')
- }
+ baseUrl: baseUrl
})
// FormData에서 SAML Response 추출
@@ -75,10 +73,7 @@ export async function POST(request: NextRequest) {
if (!samlResponse) {
debugError('No SAML Response found in request')
- const baseUrl = request.url || process.env.NEXTAUTH_URL || 'http://localhost:3000'
- return NextResponse.redirect(
- new URL('/ko/evcp', baseUrl)
- )
+ return NextResponse.redirect(new URL('/ko/evcp', baseUrl), 303)
}
// SAML Response 검증 및 파싱
@@ -99,10 +94,7 @@ export async function POST(request: NextRequest) {
})
// SAML 검증 실패 시 evcp 페이지로 리다이렉트
- const baseUrl = request.url || process.env.NEXTAUTH_URL || 'http://localhost:3000'
- return NextResponse.redirect(
- new URL('/ko/evcp', baseUrl)
- )
+ return NextResponse.redirect(new URL('/ko/evcp', baseUrl), 303)
}
// SAML 프로필을 사용자 객체로 매핑
@@ -121,8 +113,7 @@ export async function POST(request: NextRequest) {
if (!authenticatedUser) {
debugError('SAML user authentication failed')
- const baseUrl = request.url || process.env.NEXTAUTH_URL || 'http://localhost:3000'
- return NextResponse.redirect(new URL('/ko/evcp', baseUrl))
+ return NextResponse.redirect(new URL('/ko/evcp', baseUrl), 303)
}
debugSuccess('User authenticated successfully:', {
@@ -137,7 +128,38 @@ export async function POST(request: NextRequest) {
// NextAuth 세션 쿠키 설정
const cookieName = getSessionCookieName()
- const response = NextResponse.redirect(new URL('/ko/evcp', request.url || process.env.NEXTAUTH_URL || 'http://localhost:3000'))
+ // RelayState를 활용한 스마트 리다이렉트
+ let redirectPath = '/ko/evcp' // 기본값
+
+ // RelayState 안전 처리 - null, 'null', undefined, 빈 문자열 모두 처리
+ const isValidRelayState = relayState &&
+ relayState !== 'null' &&
+ relayState !== 'undefined' &&
+ relayState.trim() !== '' &&
+ typeof relayState === 'string';
+
+ if (isValidRelayState) {
+ debugLog('Using RelayState for redirect:', relayState)
+ // RelayState가 유효한 경로인지 확인
+ if (relayState.startsWith('/') && !relayState.includes('//')) {
+ redirectPath = relayState
+ } else {
+ debugLog('Invalid RelayState format, using default:', relayState)
+ }
+ } else {
+ debugLog('No valid RelayState, using default path. RelayState value:', relayState)
+ }
+
+ // URL 생성 전 최종 안전성 검사
+ if (!redirectPath || typeof redirectPath !== 'string' || redirectPath.trim() === '') {
+ redirectPath = '/ko/evcp' // 안전한 기본값으로 재설정
+ debugLog('redirectPath was invalid, reset to default:', redirectPath)
+ }
+
+ debugLog('Final redirect path:', redirectPath)
+
+ // POST 요청에 대한 응답으로는 303 See Other를 사용하여 GET으로 강제 변환
+ const response = NextResponse.redirect(new URL(redirectPath, baseUrl), 303)
response.cookies.set(cookieName, encodedToken, {
httpOnly: true,
@@ -153,10 +175,7 @@ export async function POST(request: NextRequest) {
} catch (error) {
debugError('SAML Callback processing failed:', error)
- const baseUrl = request.url || process.env.NEXTAUTH_URL || 'http://localhost:3000'
- return NextResponse.redirect(
- new URL('/ko/evcp', baseUrl)
- )
+ return NextResponse.redirect(new URL('/ko/evcp', baseUrl), 303)
}
}