@@ -289,21 +289,35 @@ <h1>🔐 OAuth2 Test Client</h1>
289289 document . getElementById ( 'oauthBtn' ) . disabled = true ;
290290 }
291291
292- // 페이지 로드 시 URL 파라미터 확인 (OAuth2 callback 처리)
292+ // 페이지 로드 시 URL Fragment 확인 (OAuth2 callback 처리)
293293 window . onload = async function ( ) {
294294 const urlParams = new URLSearchParams ( window . location . search ) ;
295+ const hash = window . location . hash . substring ( 1 ) ;
296+ const hashParams = new URLSearchParams ( hash ) ;
295297
296- if ( urlParams . has ( 'success' ) && urlParams . has ( 'state' ) ) {
297- // OAuth2 성공 - state로 토큰 요청
298- const success = urlParams . get ( 'success' ) ;
299- const state = urlParams . get ( 'state' ) ;
298+ if ( hashParams . has ( 'exchange_token' ) ) {
299+ // OAuth2 성공 - exchange token으로 실제 토큰 요청
300+ const exchangeToken = hashParams . get ( 'exchange_token' ) ;
300301
301- if ( success === 'true' && state ) {
302+ if ( exchangeToken ) {
302303 try {
303- showResult ( '🔄 토큰 요청 중...' , 'info' ) ;
304+ showResult ( '🔄 토큰 교환 중...' , 'info' ) ;
305+
306+ // Fragment 즉시 정리 (보안)
307+ window . location . hash = '' ;
304308
305309 const serverUrl = document . getElementById ( 'serverUrl' ) . value ;
306- const tokenResponse = await fetch ( `${ serverUrl } /auth/oauth2/token?state=${ encodeURIComponent ( state ) } ` ) ;
310+ const tokenResponse = await fetch ( `${ serverUrl } /auth/oauth2/exchange` , {
311+ method : 'POST' ,
312+ headers : {
313+ 'Content-Type' : 'application/json'
314+ } ,
315+ body : JSON . stringify ( {
316+ exchangeToken : exchangeToken ,
317+ clientFingerprint : generateBrowserFingerprint ( )
318+ } )
319+ } ) ;
320+
307321 const tokenData = await tokenResponse . json ( ) ;
308322
309323 if ( tokenData . success ) {
@@ -319,20 +333,33 @@ <h1>🔐 OAuth2 Test Client</h1>
319333 `Refresh Token: ${ refreshToken } \n` +
320334 `Expires In: ${ expiresIn } 초` , 'success' ) ;
321335 } else {
322- showResult ( `❌ 토큰 요청 실패: ${ tokenData . message } ` , 'error' ) ;
336+ showResult ( `❌ 토큰 교환 실패: ${ tokenData . message } ` , 'error' ) ;
323337 }
324338 } catch ( error ) {
325- showResult ( `❌ 토큰 요청 중 오류: ${ error . message } ` , 'error' ) ;
339+ showResult ( `❌ 토큰 교환 중 오류: ${ error . message } ` , 'error' ) ;
326340 }
327- } else {
328- const error = urlParams . get ( 'error' ) ;
329- showResult ( `❌ OAuth2 로그인 실패: ${ error } ` , 'error' ) ;
330341 }
331-
342+ }
343+
344+ // URL 파라미터에서 에러 확인
345+ if ( urlParams . has ( 'error' ) ) {
346+ const error = urlParams . get ( 'error' ) ;
347+ showResult ( `❌ OAuth2 로그인 실패: ${ error } ` , 'error' ) ;
348+
332349 // URL에서 파라미터 제거
333350 window . history . replaceState ( { } , document . title , window . location . pathname ) ;
334351 }
335352 } ;
353+
354+ // 간단한 브라우저 핑거프린트 생성
355+ function generateBrowserFingerprint ( ) {
356+ const screen = `${ window . screen . width } x${ window . screen . height } ` ;
357+ const timezone = Intl . DateTimeFormat ( ) . resolvedOptions ( ) . timeZone ;
358+ const language = navigator . language ;
359+ const userAgent = navigator . userAgent . substring ( 0 , 100 ) ;
360+
361+ return btoa ( `${ screen } -${ timezone } -${ language } -${ userAgent } ` ) . substring ( 0 , 32 ) ;
362+ }
336363 </ script >
337364</ body >
338365</ html >
0 commit comments