4. OAuth 2.0

(1) ์ดํ•ด

1) OAuth

์„ธ์…˜ ๊ธฐ๋ฐ˜ ์ธ์ฆ์ด ๋„๋ฆฌ ์‚ฌ์šฉ๋˜๋ฉด์„œ ์›น ์„œ๋น„์Šค์˜ ๋ณด์•ˆ์„ฑ์ด ํ–ฅ์ƒ๋˜์—ˆ์ง€๋งŒ, ์›น ์„œ๋น„์Šค ๊ฐ„ ์—ฐ๋™์ด ๋Š˜์–ด๋‚˜๋ฉด์„œ ์ƒˆ๋กœ์šด ๋ฐฉ์‹์˜ ์ธ์ฆ ๋ฉ”์ปค๋‹ˆ์ฆ˜์ด ํ•„์š”ํ•ด์กŒ๋‹ค.
OAuth(Open Authorization)๋Š” API๋ฅผ ํ†ตํ•œ ์„œ๋น„์Šค ๊ฐ„ ์ ‘๊ทผ ์œ„์ž„์— ๋Œ€ํ•œ ๊ฐœ๋ฐฉํ˜• ํ‘œ์ค€์ด๋‹ค. ์‚ฌ์šฉ์ž๊ฐ€ A ์„œ๋น„์Šค์˜ ์ •๋ณด๋ฅผ B ์„œ๋น„์Šค์—์„œ ํ™œ์šฉํ•˜๊ณ ์ž ํ•  ๋•Œ, ๊ธฐ์กด ์„œ๋น„์Šค์˜ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์ œ๊ณตํ•˜์ง€ ์•Š๊ณ ๋„ B ์„œ๋น„์Šค ์›น์‚ฌ์ดํŠธ๋‚˜ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— ์ ‘๊ทผ ๊ถŒํ•œ์„ ๋ถ€์—ฌํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐฉ์‹์œผ๋กœ, ์›น ์„œ๋น„์Šค ๊ฐ„ ์•ˆ์ „ํ•œ ๋ฐ์ดํ„ฐ ๊ณต์œ ์™€ ์ ‘๊ทผ ๊ถŒํ•œ ๊ด€๋ฆฌ๊ฐ€ ๊ฐ€๋Šฅํ•˜๋„๋ก ํ•œ๋‹ค.
2007๋…„ OAuth 1.0์ด ๋“ฑ์žฅํ•œ ๋’ค๋กœ ์›น ํ™˜๊ฒฝ์€ ๊ธ‰์ง„์ ์œผ๋กœ ์ง„ํ™”ํ–ˆ๋‹ค. ์ด์— ๋ฐœ ๋งž์ถ”์–ด 2012๋…„, ๋” ๋„“์€ ๋ฒ”์œ„์˜ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜๊ณผ ๋””๋ฐ”์ด์Šค๋ฅผ ๋ณด๋‹ค ์œ ์—ฐํ•˜๊ณ  ์•ˆ์ „ํ•˜๊ฒŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ํ”„๋กœํ† ์ฝœ์ธ OAuth 2.0์ด ๋ฐœํ‘œ๋˜์—ˆ๊ณ  ํ˜„์žฌ์—๋„ ์‚ฌ์šฉ๋˜๊ณ  ์žˆ๋‹ค.

2) ์ธ์ฆ ํ๋ฆ„

OAuth ๋ชจ๋ธ์—๋Š” ์‚ฌ์šฉ์ž(๋ฆฌ์†Œ์Šค ์†Œ์œ ์ž), ์†Œ๋น„์ž(์ œ3์ž ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜), ์„œ๋น„์Šค ์ œ๊ณต์ž, ์ธ๊ฐ€ ์„œ๋ฒ„ ์ด ๋„ค ๊ฐ€์ง€ ์ฃผ์š”ํ•œ ์—ญํ• ์ด ์ƒํ˜ธ์ž‘์šฉํ•˜๋ฉด์„œ ์ธ์ฆ๊ณผ ๊ถŒํ•œ ๋ถ€์—ฌ๊ฐ€ ์ด๋ฃจ์–ด์ง„๋‹ค.
[๊ทธ๋ฆผ 4-2] OAuth 2.0 ์ธ์ฆ ํ๋ฆ„ [๊ทธ๋ฆผ 4-2] OAuth 2.0 ์ธ์ฆ ํ๋ฆ„
[๊ทธ๋ฆผ 4-2] OAuth 2.0 ์ธ์ฆ ํ๋ฆ„
page icon
OAuth 2.0 ์ธ์ฆ ํ๋ฆ„
  1. ํด๋ผ์ด์–ธํŠธ ๋“ฑ๋ก: ์ธ์ฆ ํ๋ฆ„ ์ด์ „์— ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์€ ์„œ๋น„์Šค ์ œ๊ณต์ž์— ๋“ฑ๋กํ•˜๋Š” ๋‹จ๊ณ„๋ฅผ ๊ฑฐ์น˜๊ณ , ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์‹ ์› ํ™•์ธ ์ •๋ณด์ธ client_id์™€ client_secret์„ ๋ฐ›๋Š”๋‹ค.
  1. ๊ถŒํ•œ ์š”์ฒญ: ์†Œ๋น„์ž๊ฐ€ ์‚ฌ์šฉ์ž์—๊ฒŒ ํŠน์ • ๋ฆฌ์†Œ์Šค์— ๋Œ€ํ•œ ์ ‘๊ทผ ๊ถŒํ•œ์„ ์š”์ฒญํ•œ๋‹ค.
  1. ์‚ฌ์šฉ์ž ์ธ์ฆ: ์‚ฌ์šฉ์ž๊ฐ€ ์ธ๊ฐ€ ์„œ๋ฒ„์˜ ์ธ์ฆ ํŽ˜์ด์ง€๋กœ ๋ฆฌ๋””๋ ‰ํŠธํ•˜์—ฌ ๋กœ๊ทธ์ธํ•˜๊ณ  ์ž์‹ ์˜ ๊ถŒํ•œ ์ œ๊ณต์— ๋™์˜ํ•œ๋‹ค. ๋ฆฌ์†Œ์Šค ์œ„์ž„ ์š”์ฒญ์—๋Š” client_id, redirect_uri, response_type, scope ๋“ฑ์ด ํฌํ•จ๋œ๋‹ค.
  1. ์ธ๊ฐ€ ๊ทธ๋žœํŠธ ๋ฐœ๊ธ‰: ์‚ฌ์šฉ์ž๊ฐ€ ๋ฆฌ์†Œ์Šค ์œ„์ž„์„ ์Šน์ธํ•˜๋ฉด, ์†Œ๋น„์ž๋Š” ์ธ๊ฐ€ ์„œ๋ฒ„๋กœ๋ถ€ํ„ฐ ๐Ÿ”‘์ธ๊ฐ€ ๊ทธ๋žœํŠธ๋ฅผ ๋ฐ›๊ณ  redirect_uri๋กœ ์‚ฌ์šฉ์ž๋ฅผ ๋ฆฌ๋””๋ ‰ํŠธํ•œ๋‹ค.
  1. ํ† ํฐ ์š”์ฒญ: ์†Œ๋น„์ž๋Š” ์„œ๋ฒ„ ๊ฐ„ ํ†ต์‹ ์„ ํ†ตํ•ด ๐Ÿ”‘์ธ๊ฐ€ ๊ทธ๋žœํŠธ๋ฅผ ์ธ๊ฐ€ ์„œ๋ฒ„์— ์ œ์‹œํ•˜์—ฌ ๐Ÿช™์ ‘๊ทผ ํ† ํฐ์„ ์š”์ฒญํ•œ๋‹ค. ์ด ์š”์ฒญ์—๋Š” client_id, client_secret, ์ธ๊ฐ€ ๊ทธ๋žœํŠธ ๋“ฑ์ด ํฌํ•จ๋œ๋‹ค.
  1. ํ† ํฐ ๋ฐœ๊ธ‰: ์ธ๊ฐ€ ์„œ๋ฒ„๋Š” ์†Œ๋น„์ž์™€ ๐Ÿ”‘์ธ๊ฐ€ ๊ทธ๋žœํŠธ๋ฅผ ๊ฒ€์ฆํ•˜๊ณ  ๐Ÿช™์ ‘๊ทผ ํ† ํฐ๊ณผ ๐Ÿ”๊ฐฑ์‹  ํ† ํฐ์„ ๋ฐœ๊ธ‰ํ•œ๋‹ค.
  1. ๋ฆฌ์†Œ์Šค ์ ‘๊ทผ ์š”์ฒญ: ์†Œ๋น„์ž๋Š” ์„œ๋น„์Šค ์ œ๊ณต์ž์— ๐Ÿช™์ ‘๊ทผ ํ† ํฐ์„ ์ œ์‹œํ•˜๊ณ  ๋ณดํ˜ธ๋œ ๋ฆฌ์†Œ์Šค์— ์ ‘๊ทผ์„ ์š”์ฒญํ•œ๋‹ค.
  1. ๋ฆฌ์†Œ์Šค ์ ‘๊ทผ ํ—ˆ์šฉ:์„œ๋น„์Šค ์ œ๊ณต์ž๋Š” ์ ‘๊ทผ ํ† ํฐ๊ณผ ๋ฆฌ์†Œ์Šค ์ ‘๊ทผ ๊ถŒํ•œ์„ ๊ฒ€์ฆํ•˜๊ณ , ์œ ํšจํ•  ๊ฒฝ์šฐ ๋ฆฌ์†Œ์Šค ์ ‘๊ทผ์„ ํ—ˆ์šฉํ•œ๋‹ค.
  1. ํ† ํฐ ๊ฐฑ์‹ : ์•ก์„ธ์Šค ํ† ํฐ์ด ๋งŒ๋ฃŒ๋˜๋ฉด, ์†Œ๋น„์ž๋Š” ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ๐Ÿ”๊ฐฑ์‹  ํ† ํฐ์„ ์‚ฌ์šฉํ•˜์—ฌ ์ƒˆ๋กœ์šด ์ ‘๊ทผ ํ† ํฐ์„ ์š”์ฒญํ•œ๋‹ค.
ย 
์˜ˆ๋ฅผ ๋“ค์–ด, ํ•œ ์œ ์ €๊ฐ€ ์ƒˆ๋กœ์šด ์•ฑ A์— 'Google ๊ณ„์ •์œผ๋กœ ๋กœ๊ทธ์ธ'ํ•˜๋Š” ๊ฒฝ์šฐ๋ฅผ ์ƒ๊ฐํ•ด๋ณด์ž. ์ด ๊ณผ์ •์—์„œ ๋กœ๊ทธ์ธ์„ ์‹œ๋„ํ•˜๋Š” ์œ ์ €๊ฐ€ ์‚ฌ์šฉ์ž, ์•ฑ A๋Š” ์†Œ๋น„์ž, Google์ด ์„œ๋น„์Šค ์ œ๊ณต์ž์ด์ž ์ธ๊ฐ€ ์„œ๋ฒ„๊ฐ€ ๋œ๋‹ค. ์ด๋Ÿฌํ•œ ๊ธฐ๋Šฅ์€ ๋ณดํ†ต ์•„๋ž˜์™€ ๊ฐ™์€ ๊ฐ„ํŽธ ๋กœ๊ทธ์ธ ๋ฉ”๋‰ด๋ฅผ ํ†ตํ•ด ์‹œ๋„ํ•  ์ˆ˜ ์žˆ๋‹ค.
[๊ทธ๋ฆผ 4-3] OAuth์„ ์‚ฌ์šฉํ•œ SNS ๊ฐ„ํŽธ ๋กœ๊ทธ์ธ[๊ทธ๋ฆผ 4-3] OAuth์„ ์‚ฌ์šฉํ•œ SNS ๊ฐ„ํŽธ ๋กœ๊ทธ์ธ
[๊ทธ๋ฆผ 4-3] OAuth์„ ์‚ฌ์šฉํ•œ SNS ๊ฐ„ํŽธ ๋กœ๊ทธ์ธ
A ์•ฑ์€ ๊ฐœ๋ฐœ ๋‹จ๊ณ„์—์„œ Google์— ํด๋ผ์ด์–ธํŠธ๋กœ ๋“ฑ๋กํ•˜๋Š” ๋‹จ๊ณ„๋ฅผ ๊ฑฐ์ณค์„ ๊ฒƒ์ด๋‹ค. ๋กœ๊ทธ์ธ ๋ฉ”๋‰ด์—์„œ Google ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด๋ฉด A ์•ฑ์€ ์‚ฌ์šฉ์ž๋ฅผ Google์˜ OAuth ์ธ๊ฐ€ ์„œ๋ฒ„๋กœ ๋ฆฌ๋””๋ ‰ํŠธํ•œ๋‹ค. ์‚ฌ์šฉ์ž๋Š” Google ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€์—์„œ ๊ณ„์ • ์ •๋ณด๋ฅผ ์„ ํƒํ•ด์„œ ์ธ์ฆํ•œ๋‹ค.
ย 
[๊ทธ๋ฆผ 4-4] Google ์ธ์ฆ ํŽ˜์ด์ง€๋กœ ๋ฆฌ๋””๋ ‰ํŠธ[๊ทธ๋ฆผ 4-4] Google ์ธ์ฆ ํŽ˜์ด์ง€๋กœ ๋ฆฌ๋””๋ ‰ํŠธ
[๊ทธ๋ฆผ 4-4] Google ์ธ์ฆ ํŽ˜์ด์ง€๋กœ ๋ฆฌ๋””๋ ‰ํŠธ
[๊ทธ๋ฆผ 4-5] Google ๋กœ๊ทธ์ธ ํ™”๋ฉด[๊ทธ๋ฆผ 4-5] Google ๋กœ๊ทธ์ธ ํ™”๋ฉด
[๊ทธ๋ฆผ 4-5] Google ๋กœ๊ทธ์ธ ํ™”๋ฉด
Google์€ ์‚ฌ์šฉ์ž์—๊ฒŒ A ์•ฑ์ด ์š”์ฒญํ•˜๋Š” ๊ถŒํ•œ์— ๋Œ€ํ•ด ๋™์˜๋ฅผ ์š”์ฒญํ•œ๋‹ค. ์‚ฌ์šฉ์ž๊ฐ€ ๋™์˜ํ•˜๋ฉด Google ์ธ๊ฐ€ ์„œ๋ฒ„๋Š” A ์•ฑ์— ์‚ฌ์šฉ์ž์˜ ์Šน์ธ์„ ๋‚˜ํƒ€๋‚ด๋Š” ์ธ๊ฐ€ ๊ทธ๋žœํŠธ๋ฅผ ๋ฐœ๊ธ‰ํ•œ๋‹ค.
[๊ทธ๋ฆผ 4-6] A ์•ฑ์— ๋Œ€ํ•œ ๊ถŒํ•œ ๋™์˜ ํ™”๋ฉด[๊ทธ๋ฆผ 4-6] A ์•ฑ์— ๋Œ€ํ•œ ๊ถŒํ•œ ๋™์˜ ํ™”๋ฉด
[๊ทธ๋ฆผ 4-6] A ์•ฑ์— ๋Œ€ํ•œ ๊ถŒํ•œ ๋™์˜ ํ™”๋ฉด
์‚ฌ์šฉ์ž๋Š” A ์•ฑ์œผ๋กœ ๋ฆฌ๋””๋ ‰ํŠธ ๋œ๋‹ค. A ์•ฑ์€ ์„œ๋ฒ„ ๊ฐ„ ํ†ต์‹ ์„ ํ†ตํ•ด ์ธ๊ฐ€ ๊ทธ๋žœํŠธ๋กœ Google ์ธ๊ฐ€ ์„œ๋ฒ„์— ์ ‘๊ทผ ํ† ํฐ์„ ์š”์ฒญํ•œ๋‹ค. ์ธ๊ฐ€ ์„œ๋ฒ„๋Š” ์ธ๊ฐ€ ๊ทธ๋žœํŠธ์˜ ์œ ํšจ์„ฑ์„ ๊ฒ€์ฆํ•˜๊ณ  A ์•ฑ์—๊ฒŒ ์ ‘๊ทผ ํ† ํฐ๊ณผ ๊ฐฑ์‹  ํ† ํฐ์„ ๋ฐœ๊ธ‰ํ•œ๋‹ค(์ธ๊ฐ€ ๊ทธ๋žœํŠธ โ†” ํ† ํฐ). A ์•ฑ์€ ์ ‘๊ทผ ํ† ํฐ์„ ์‚ฌ์šฉํ•ด Google์—๊ฒŒ ์‚ฌ์šฉ์ž์˜ ๊ณ„์ • ์ •๋ณด ์ผ๋ถ€๋ฅผ ์š”์ฒญํ•œ๋‹ค. Google์€ ์ ‘๊ทผ ํ† ํฐ์˜ ์œ ํšจ์„ฑ๊ณผ ๋ฆฌ์†Œ์Šค ๊ถŒํ•œ์„ ๊ฒ€์ฆํ•œ ๋’ค, ์š”์ฒญ๋œ ์‚ฌ์šฉ์ž ๊ณ„์ • ์ •๋ณด๋ฅผ A ์•ฑ์—๊ฒŒ ์ œ๊ณตํ•œ๋‹ค(์ ‘๊ทผ ํ† ํฐ โ†” ๊ณ„์ • ์ •๋ณด). A ์•ฑ์€ Google์˜ ์ •๋ณด๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ ์‚ฌ์šฉ์ž์˜ ๋กœ๊ทธ์ธ ํ”„๋กœ์„ธ์Šค๋ฅผ ์™„๋ฃŒํ•œ๋‹ค. ์ดํ›„ ์ ‘๊ทผ ํ† ํฐ์ด ๋งŒ๋ฃŒ๋˜๋ฉด, A ์•ฑ์€ ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ๊ฐฑ์‹  ํ† ํฐ์„ ์‚ฌ์šฉํ•˜์—ฌ ์ƒˆ๋กœ์šด ์ ‘๊ทผ ํ† ํฐ์„ ์š”์ฒญํ•œ๋‹ค(๊ฐฑ์‹  ํ† ํฐ โ†” ์ ‘๊ทผ ํ† ํฐ). ์ด๋ฅผ ํ†ตํ•ด ์‚ฌ์šฉ์ž๋Š” ๋‹ค์‹œ ๋กœ๊ทธ์ธํ•  ํ•„์š” ์—†์ด ์ง€์†์ ์œผ๋กœ ์„œ๋น„์Šค๋ฅผ ์ด์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.
OAuth ์šฉ์–ด ์ •๋ฆฌ
OAuth ์šฉ์–ด ์ •๋ฆฌ
  • ์‚ฌ์šฉ์ž(User/Resource Owner): ๋ณดํ˜ธ๋œ ๋ฆฌ์†Œ์Šค์— ๋Œ€ํ•œ ์ ‘๊ทผ ๊ถŒํ•œ์„ ๊ฐ€์ง„ ์‚ฌ์šฉ์ž
  • ์†Œ๋น„์ž(Consumer): ๋ณดํ˜ธ๋œ ๋ฆฌ์†Œ์Šค์— ์ ‘๊ทผํ•˜๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜. ์š”์ฒญ์ž(Client)์˜ ์—ญํ• 
  • ์„œ๋น„์Šค ์ œ๊ณต์ž(Service Provider): ๋ณดํ˜ธ๋œ ๋ฆฌ์†Œ์Šค๋ฅผ ์ œ๊ณตํ•˜๋Š” ์„œ๋ฒ„(API Server)
  • ์ธ๊ฐ€ ์„œ๋ฒ„(Authorization Server): ์†Œ๋น„์ž์˜ ์ธ์ฆ ๋ฐ ๊ถŒํ•œ ๋ถ€์—ฌ๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ์„œ๋ฒ„. ์„œ๋น„์Šค ์ œ๊ณต์ž์™€ ๋™์ผํ•  ์ˆ˜ ์žˆ์Œ
  • ์ ‘๊ทผ ํ† ํฐ(Access Token): ๋ณดํ˜ธ๋œ ๋ฆฌ์†Œ์Šค์— ๋Œ€ํ•œ ์ ‘๊ทผ ๊ถŒํ•œ์„ ๋‚˜ํƒ€๋‚ด๋Š” ์ž๊ฒฉ ์ฆ๋ช…
  • ์ธ๊ฐ€ ๊ทธ๋žœํŠธ(Authorization Grant): ์ตœ์ดˆ ์ธ์ฆ ์‹œ ์ ‘๊ทผ ํ† ํฐ์„ ์–ป๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉํ•˜๋Š” ์ž๊ฒฉ ์ฆ๋ช…. ์‚ฌ์šฉ์ž์˜ ์Šน์ธ์„ ๋‚˜ํƒ€๋ƒ„
  • ๊ฐฑ์‹  ํ† ํฐ(Refresh Token): ์ ‘๊ทผ ํ† ํฐ ๋งŒ๋ฃŒ ํ›„ ์žฌ์ธ์ฆ ์‹œ ์ ‘๊ทผ ํ† ํฐ์„ ์–ป๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉํ•˜๋Š” ์ž๊ฒฉ ์ฆ๋ช…
ย 

(2) ํ”„๋ก ํŠธ์—”๋“œ์˜ OAuth 2.0 ๊ตฌํ˜„๊ณผ ๋ณด์•ˆ

OAuth 2.0์—์„œ ํ”„๋ก ํŠธ์—”๋“œ์˜ ์ฃผ์š” ์—ญํ• ์€ ์‚ฌ์šฉ์ž๋ฅผ ์ธ์ฆ ์„œ๋ฒ„๋กœ ๋ฆฌ๋””๋ ‰ํŠธํ•˜๊ณ , ๊ทธ๋žœํŠธ ์œ ํ˜•์— ๋”ฐ๋ฅธ ์ธ์ฆ ํ๋ฆ„์„ ์ฒ˜๋ฆฌํ•˜๋ฉฐ, ๋ฐ›์€ ํ† ํฐ์„ ์•ˆ์ „ํ•˜๊ฒŒ ๊ด€๋ฆฌํ•˜๋Š” ๊ฒƒ์ด๋‹ค.

1) ๋ฆฌ๋””๋ ‰ํŠธ ๋ณด์•ˆ

OAuth 2.0์˜ ์ธ์ฆ ๊ณผ์ •์—๋Š” ์ธ์ฆ ์„œ๋ฒ„๋กœ์˜ ๋ฆฌ๋””๋ ‰์…˜์ด ํฌํ•จ๋œ๋‹ค. ์•ž์„œ ์—ฌ๋Ÿฌ ๋ณด์•ˆ ์œ„ํ˜‘์—์„œ ์‚ดํŽด๋ณธ ๊ฒƒ์ฒ˜๋Ÿผ, ๋ฆฌ๋””๋ ‰ํŠธ ๊ธฐ๋Šฅ์€ ๋ณด์•ˆ ์œ„ํ—˜์„ ์ดˆ๋ž˜ํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ์ ์ ˆํ•œ ๋ณด์•ˆ ์ฒ˜๋ฆฌ๊ฐ€ ํ•„์š”ํ•˜๋‹ค.
  1. ๋ฆฌ๋””๋ ‰ํŠธ URI ๊ฒ€์ฆ
    1. ๋™์ ์œผ๋กœ ์ƒ์„ฑ๋œ ๋ฆฌ๋””๋ ‰ํŠธ URI๋Š” ๋ณด์•ˆ ์œ„ํ—˜์ด ์žˆ์œผ๋ฏ€๋กœ ์ •์  ๋ฆฌ๋””๋ ‰ํŠธ URI๋งŒ์„ ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค. 'Google ๊ณ„์ •์œผ๋กœ ๋กœ๊ทธ์ธ' ์˜ˆ์‹œ์—์„œ๋Š” ๋ฏธ๋ฆฌ ๋“ฑ๋กํ•ด ๋‘” redirect_uri๋กœ ๋ฆฌ๋””๋ ‰ํŠธ๋œ๋‹ค.
  1. ์ƒํƒœ(State) ๋งค๊ฐœ๋ณ€์ˆ˜ ์‚ฌ์šฉ
    1. CSRF ๊ณต๊ฒฉ์„ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•ด ์ƒํƒœ(state) ๋งค๊ฐœ๋ณ€์ˆ˜ ์‚ฌ์šฉ์„ ๊ณ ๋ คํ•  ์ˆ˜ ์žˆ๋‹ค. ์ƒํƒœ ๋งค๊ฐœ๋ณ€์ˆ˜๋Š” ๋žœ๋คํ•˜๊ฒŒ ์ƒ์„ฑ๋œ ๋ฌธ์ž์—ด๋กœ CSRF ํ† ํฐ๊ณผ ์œ ์‚ฌํ•œ ์—ญํ• ์„ ํ•œ๋‹ค.
      ์ด ๊ฐ’์€ ํด๋ผ์ด์–ธํŠธ ์š”์ฒญ ์‹œ ์ƒ์„ฑ๋˜์–ด ์š”์ฒญ์— ํฌํ•จ๋˜๊ณ , ์ธ์ฆ ์„œ๋ฒ„๋Š” ์‚ฌ์šฉ์ž ์ธ์ฆ ์™„๋ฃŒ ํ›„ ์‘๋‹ต์— ์ด ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ํฌํ•จํ•˜์—ฌ ์ „๋‹ฌํ•œ๋‹ค. ํด๋ผ์ด์–ธํŠธ๋Š” ์š”์ฒญ๊ณผ ์‘๋‹ต์˜ ์ƒํƒœ ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ๋น„๊ตํ•˜์—ฌ ๋™์ผ ์„ธ์…˜ ์—ฌ๋ถ€๋ฅผ ๊ฒ€์ฆํ•œ๋‹ค.

2) ๊ทธ๋žœํŠธ ์œ ํ˜•(Grant Types)

์‚ฌ์šฉ์ž์˜ ์Šน์ธ์„ ๋‚˜ํƒ€๋‚ด๋Š” ์ธ๊ฐ€ ๊ทธ๋žœํŠธ์—๋Š” ์—ฌ๋Ÿฌ ์ข…๋ฅ˜๊ฐ€ ์žˆ์–ด์„œ ๋‹ค์–‘ํ•œ ์ธ์ฆ ์‹œ๋‚˜๋ฆฌ์˜ค์— ๋Œ€์‘ํ•  ์ˆ˜ ์žˆ๋‹ค. ํ”„๋ก ํŠธ์—”๋“œ๋Š” ์„œ๋น„์Šค ์ œ๊ณต์ž๊ฐ€ ์ง€์›ํ•˜๋Š” ๊ทธ๋žœํŠธ ์œ ํ˜•์„ ์ดํ•ดํ•˜๊ณ  ๊ทธ์— ๋งž๋Š” ์ธ์ฆ ํ๋ฆ„์„ ๊ตฌํ˜„ํ•ด์•ผ ํ•œ๋‹ค. ์ฃผ์š” ๊ทธ๋žœํŠธ ์œ ํ˜•๋“ค์€ ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.
  1. ์ธ์ฆ ์ฝ”๋“œ ๊ทธ๋žœํŠธ
    1. ํด๋ผ์ด์–ธํŠธ๊ฐ€ ๊ทธ๋žœํŠธ๋กœ ์ธ์ฆ ์ฝ”๋“œ๋ฅผ ๋ฐ›๊ณ , ์ด๋ฅผ ํ†ตํ•ด ์ ‘๊ทผ ํ† ํฐ์„ ์–ป๋Š” ๋ฐฉ์‹. ๊ฐ€์žฅ ๋Œ€ํ‘œ์ ์ธ ๊ทธ๋žœํŠธ ์œ ํ˜•์œผ๋กœ ๋น„๊ต์  ๋” ์•ˆ์ „ํ•˜๋‹ค. ์œ„์˜ 'Google ๊ณ„์ •์œผ๋กœ ๋กœ๊ทธ์ธ' ์˜ˆ์‹œ์—์„œ ์‚ฌ์šฉํ•œ ๋ฐฉ๋ฒ•์ด ์ด๊ฒƒ์ด๋‹ค.
  1. ์•”๋ฌต์  ๊ทธ๋žœํŠธ
    1. ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์ธ์ฆ ์ฝ”๋“œ ์—†์ด ์ง์ ‘ ์ ‘๊ทผ ํ† ํฐ์„ ์–ป๋Š” ๋ฐฉ์‹. ๋กœ์ง์ด ๊ฐ„๋‹จํ•˜์ง€๋งŒ ์ƒ๋Œ€์ ์œผ๋กœ ๋ณด์•ˆ์„ฑ์ด ๋‚ฎ๋‹ค.
  1. PKCE(Proof Key for Code Exchange)
    1. ์ธ์ฆ ์ฝ”๋“œ ๊ทธ๋žœํŠธ์˜ ๋ณด์•ˆ ๊ฐ•ํ™” ๋ฒ„์ „์œผ๋กœ, ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์ฝ”๋“œ๋ฅผ ๊ตํ™˜ํ•  ๋•Œ ์ถ”๊ฐ€์ ์ธ ๋ณด์•ˆ ๊ณ„์ธต์„ ์ œ๊ณตํ•˜๋Š” ๋ฐฉ์‹

3) ํ† ํฐ ์ €์žฅ๊ณผ ๊ฐฑ์‹ 

ํ† ํฐ(Token)ยนโพ์€ ์‚ฌ์šฉ์ž์˜ ์ธ์ฆ ์ •๋ณด๋ฅผ ์˜๋ฏธํ•˜๋Š” ๋ฌธ์ž์—ด์ด๋‹ค. ํ† ํฐ์—๋Š” ์ผ๋ฐ˜์ ์œผ๋กœ ์‚ฌ์šฉ์ž ID, ๊ถŒํ•œ ์ •๋ณด, ๋งŒ๋ฃŒ ์‹œ๊ฐ„ ๋“ฑ์˜ ์ •๋ณด๊ฐ€ ํฌํ•จ๋˜์–ด ์žˆ๋‹ค. OAuth 2.0์—์„œ๋Š” ์ ‘๊ทผ ํ† ํฐ๊ณผ ๊ฐฑ์‹  ํ† ํฐ์„ ๋‚˜๋ˆ„์–ด ๊ด€๋ฆฌํ•จ์œผ๋กœ์จ ํ† ํฐ ํƒˆ์ทจ์‹œ์˜ ํ”ผํ•ด๋ฅผ ์ค„์ธ๋‹ค.

โ‘  ์ ‘๊ทผ ํ† ํฐ(Access Token)

1์‹œ๊ฐ„ ์ด๋‚ด์˜ ์งง์€ ์ˆ˜๋ช…์„ ๊ฐ€์ง€๋ฉฐ, ์ผ๋ฐ˜์ ์œผ๋กœ ํด๋ผ์ด์–ธํŠธ ์ธก ๋ฉ”๋ชจ๋ฆฌ์— ์ €์žฅ๋œ๋‹ค. ์ด๋Š” API ์š”์ฒญ๋งˆ๋‹ค ๋น ๋ฅด๊ฒŒ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ์ง€๋งŒ ๋™์‹œ์— XSS ๊ณต๊ฒฉ์— ์ทจ์•ฝํ•˜๋‹ค. ๋” ์•ˆ์ „ํ•œ ๋ฐฉ๋ฒ•์œผ๋กœ HttpOnly ์ฟ ํ‚ค์— ๋ณด๊ด€์„ ๊ณ ๋ คํ•  ์ˆ˜๋„ ์žˆ๋‹ค.

โ‘ก ๊ฐฑ์‹  ํ† ํฐ(Refresh Token)

์ ‘๊ทผ ํ† ํฐ๋ณด๋‹ค ์ˆ˜๋ช…์ด ๊ธธ๊ณ , ์ ‘๊ทผ ํ† ํฐ ๋งŒ๋ฃŒ ์‹œ ์ƒˆ๋กœ์šด ์ ‘๊ทผ ํ† ํฐ์„ ๋ฐœ๊ธ‰ ๋ฐ›์„ ๋•Œ ์‚ฌ์šฉํ•œ๋‹ค(๊ฐฑ์‹  ํ† ํฐ โ†” ์ ‘๊ทผ ํ† ํฐ). ์ผ๋ฐ˜์ ์œผ๋กœ HttpOnly/Secure ์ฟ ํ‚ค์— ์ €์žฅ๋˜๋ฉฐ, ํ•„์š”์‹œ์—” ๊ฐ’์„ ์•”ํ˜ธํ™”ํ•ด ๋กœ์ปฌ ์Šคํ† ๋ฆฌ์ง€์— ์ €์žฅํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ๊ณ ๋ คํ•  ์ˆ˜ ์žˆ๋‹ค.

โ‘ข ํ† ํฐ ๋งŒ๋ฃŒ ๊ฐ์ง€ ๋ฐ ์ž๋™ ๊ฐฑ์‹ 

ํ”„๋ก ํŠธ์—”๋“œ์—์„œ๋Š” ์ ‘๊ทผ ํ† ํฐ์˜ ๋งŒ๋ฃŒ๋ฅผ ๊ฐ์ง€ํ•˜๊ณ  ๊ฐฑ์‹  ์š”์ฒญ์„ ๋ณด๋‚ด๋Š” ์ž‘์—…์ด ํ•„์š”ํ•˜๋‹ค. ์ด๋Š” ์„œ๋ฒ„ ์‘๋‹ต์˜ ์ƒํƒœ ์ฝ”๋“œ๋‚˜ ํ† ํฐ์— ํฌํ•จ๋œ ๋งŒ๋ฃŒ ์‹œ๊ฐ„์„ ํ™•์ธํ•˜์—ฌ ์ด๋ฃจ์–ด์ง„๋‹ค. ํ† ํฐ ๋งŒ๋ฃŒ๊ฐ€ ๊ฐ์ง€๋˜๋ฉด, ์ €์žฅ๋œ ๊ฐฑ์‹  ํ† ํฐ์„ ์‚ฌ์šฉํ•˜์—ฌ ์ธ์ฆ ์„œ๋ฒ„์— ์š”์ฒญ์„ ๋ณด๋‚ด๊ณ  ์ƒˆ๋กœ์šด ์ ‘๊ทผ ํ† ํฐ์„ ๋ฐœ๊ธ‰ ๋ฐ›๋Š”๋‹ค. ์ƒˆ ์ ‘๊ทผ ํ† ํฐ์€ ์•ˆ์ „ํ•˜๊ฒŒ ์ €์žฅ๋˜์–ด ์ดํ›„์˜ API ์š”์ฒญ์— ์‚ฌ์šฉ๋œ๋‹ค. ์ด ๊ณผ์ •์€ ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์„ ๋ฐฉํ•ดํ•˜์ง€ ์•Š๊ณ  ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ์ž๋™์œผ๋กœ ์ˆ˜ํ–‰๋˜์–ด์•ผ ํ•œ๋‹ค.

ยนโพ ํ† ํฐ์— ๋Œ€ํ•œ ์ž์„ธํ•œ ์„ค๋ช…๊ณผ ์˜ˆ์‹œ ์ฝ”๋“œ๋Š”
5. ํ† ํฐ ๊ธฐ๋ฐ˜ ์ธ์ฆ
์„ ์ฐธ๊ณ ํ•œ๋‹ค.
ย 

(3) ์‹ค์Šต

1) OAuth 2.0 ๊ตฌํ˜„์‹œ ๊ณ ๋ ค ์‚ฌํ•ญ

  • ํด๋ผ์ด์–ธํŠธ ์‹œํฌ๋ฆฟ์ด ํ”„๋ก ํŠธ์—”๋“œ์— ๋…ธ์ถœ๋˜์ง€ ์•Š๋„๋ก ํ•œ๋‹ค.
  • ๋ฆฌ๋””๋ ‰ํŠธ URI๋Š” ๋“ฑ๋ก๋œ URI์™€ ์ •ํ™•ํžˆ ์ผ์น˜ํ•˜๋„๋ก ํ•œ๋‹ค.
  • ์š”๊ตฌ์‚ฌํ•ญ์— ๋”ฐ๋ผ ํ† ํฐ์„ ์•ˆ์ „ํ•˜๊ฒŒ ์ €์žฅํ•  ์œ„์น˜๋ฅผ ๊ฒฐ์ •ํ•œ๋‹ค.
  • CLIENT_SECRET์„ ํฌํ•จํ•œ ์ž‘์—…์€ ๋ฐ˜๋“œ์‹œ ์„œ๋ฒ„ ์ธก์—์„œ ์ฒ˜๋ฆฌํ•œ๋‹ค.

2) ํ”„๋ก ํŠธ์—”๋“œ ์˜ˆ์‹œ ์ฝ”๋“œ

HTML๊ณผ JavaScript๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ Google ๊ณ„์ •์„ ํ†ตํ•œ ๊ฐ„๋‹จํ•œ ์†Œ์…œ ๋กœ๊ทธ์ธ ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•ด๋ณด์ž.
'Google ๊ณ„์ •์œผ๋กœ ๋กœ๊ทธ์ธ' ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•˜๋ ค๋ฉด ๋จผ์ € Google Cloud Console์—์„œ ํ”„๋กœ์ ํŠธ๋ฅผ ์„ค์ •ํ•˜๊ณ  ํด๋ผ์ด์–ธํŠธ ID๋ฅผ ๋ฐœ๊ธ‰ํ•˜๋Š” ๊ณผ์ •์ด ํ•„์š”ํ•˜๋‹ค.
ํ”„๋กœ์ ํŠธ ์„ค์ •๊ณผ ํด๋ผ์ด์–ธํŠธ ID ๋ฐœ๊ธ‰
ํ”„๋กœ์ ํŠธ ์„ค์ •๊ณผ ํด๋ผ์ด์–ธํŠธ ID ๋ฐœ๊ธ‰
  1. https://console.cloud.google.com/์— ์ ‘์†ํ•ด์„œ ์ƒˆ ํ”„๋กœ์ ํŠธ๋ฅผ ์ƒ์„ฑํ•˜๊ฑฐ๋‚˜ ๊ธฐ์กด ํ”„๋กœ์ ํŠธ๋ฅผ ์„ ํƒํ•จ(๋กœ๊ณ  ์šฐ์ธก ์„ ํƒ๊ธฐ์—์„œ ํ”„๋กœ์ ํŠธ ์„ ํƒ ๊ฐ€๋Šฅ)
    1. notion imagenotion image
  1. ์ขŒ์ƒ๋‹จ ๋ฉ”๋‰ด ์•„์ด์ฝ˜์„ ๋ˆ„๋ฅด๊ณ  API ๋ฐ ์„œ๋น„์Šค>์‚ฌ์šฉ์ž ์ธ์ฆ ์ •๋ณด๋กœ ์ด๋™
    1. notion imagenotion image
  1. ์‚ฌ์šฉ์ž ์ธ์ฆ ์ •๋ณด ๋งŒ๋“ค๊ธฐ>OAuth ํด๋ผ์ด์–ธํŠธ ID๋ฅผ ์„ ํƒ
    1. notion imagenotion image
  1. ๋™์˜ ํ™”๋ฉด ๊ตฌ์„ฑ>User Type: ์™ธ๋ถ€>๋งŒ๋“ค๊ธฐ๋ฅผ ์„ ํƒํ•˜๊ณ , ์•ฑ ์ด๋ฆ„/์ด๋ฉ”์ผ/๊ฐœ๋ฐœ์ž ์—ฐ๋ฝ์ฒ˜ ๋“ฑ์„ ์ž…๋ ฅํ•œ ๋’ค ์ €์žฅ
  1. ๋ฒ”์œ„ ์ถ”๊ฐ€ ๋˜๋Š” ์‚ญ์ œ๋ฅผ ๋ˆŒ๋Ÿฌ '../auth/userinfo.profile' ๋“ฑ ์›ํ•˜๋Š” ์ •๋ณด ๋ฒ”์œ„๋ฅผ ์„ ํƒํ•˜๊ณ  ์—…๋ฐ์ดํŠธ์™€ ์ €์žฅ
    1. notion imagenotion image
  1. ํ•„์š”ํ•œ ๊ฒฝ์šฐ ํ…Œ์ŠคํŠธ ์‚ฌ์šฉ์ž๋ฅผ ์ถ”๊ฐ€ํ•˜๊ณ  ์ €์žฅํ•œ ๋’ค ์š”์•ฝ ์ •๋ณด ํ™•์ธ
  1. ์‚ฌ์šฉ์ž ์ธ์ฆ ์ •๋ณด ๋งŒ๋“ค๊ธฐ>OAuth ํด๋ผ์ด์–ธํŠธ ID๋กœ ๋Œ์•„๊ฐ€์„œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์œ ํ˜•์œผ๋กœ ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์„ ํƒ ํ›„ ์•ฑ ์ด๋ฆ„ ์ž…๋ ฅ
    1. notion imagenotion image
  1. '์Šน์ธ๋œ JavaScript ์›๋ณธ'์— ์›น ์„œ๋น„์Šค ์ฃผ์†Œ, '์Šน์ธ๋œ ๋ฆฌ๋””๋ ‰์…˜ URI'์— ๋กœ๊ทธ์ธ ํ›„ ๋ฆฌ๋””๋ ‰ํŠธํ•  ์ฃผ์†Œ๋ฅผ ์ž…๋ ฅ ํ›„ ๋งŒ๋“ค๊ธฐ ์„ ํƒ
    1. notion imagenotion image
      notion imagenotion image
  1. 'OAuth ํด๋ผ์ด์–ธํŠธ ์ƒ์„ฑ๋จ' ํŒ์—…์ฐฝ์—์„œ 'ํด๋ผ์ด์–ธํŠธ ID'์™€ 'ํด๋ผ์ด์–ธํŠธ ๋ณด์•ˆ ๋น„๋ฐ€๋ฒˆํ˜ธ'๋ฅผ ํ™•์ธํ•˜๊ณ  ๋ณต์‚ฌํ•ด ๋‘ 
ย 
์•„๋ž˜ ์ฝ”๋“œ๋Š” ์ธ์ฆ ์ฝ”๋“œ ๊ทธ๋žœํŠธ ๋ฐฉ์‹์„ ์‚ฌ์šฉํ•˜์—ฌ ์ธ์ฆ ์ฝ”๋“œ๋ฅผ ์ ‘๊ทผ ํ† ํฐ๊ณผ ๊ฐฑ์‹  ํ† ํฐ์œผ๋กœ ๊ตํ™˜ํ•˜๋Š” ๊ณผ์ •์„ ๊ฑฐ์นœ๋‹ค. ์ด ์˜ˆ์‹œ์—์„œ ๋‘ ํ† ํฐ์€ ์ž„์˜๋กœ ๋กœ์ปฌ ๋ณ€์ˆ˜๋กœ ์ €์žฅํ•˜๊ณ , ๋ฐฑ์—”๋“œ ์—†์ด ์ฝ”๋“œ๊ฐ€ ๋™์ž‘ํ•˜๋„๋ก CLIENT_SECRET์„ ํด๋ผ์ด์–ธํŠธ ์ธก์—์„œ ๋‹ค๋ฃจ์—ˆ๋‹ค.
๊ทธ๋Ÿฌ๋‚˜ ์‹ค์ œ ๊ตฌํ˜„์‹œ์—๋Š” ํ† ํฐ์˜ ๋” ์•ˆ์ „ํ•œ ์ €์žฅ ์œ„์น˜๋ฅผ ๊ณ ๋ คํ•ด์•ผ ํ•˜๋ฉฐ, CLIENT_SECRET์ด ํด๋ผ์ด์–ธํŠธ ์ธก์— ์ ˆ๋Œ€ ๋…ธ์ถœ๋˜์ง€ ์•Š๋„๋ก, ํ† ํฐ ๊ตํ™˜๊ณผ ํ† ํฐ ๊ฐฑ์‹  ๊ณผ์ •์„ ๋ฐฑ์—”๋“œ์—์„œ ์ˆ˜ํ–‰ํ•ด์•ผ ํ•œ๋‹ค.(์ž„์˜์˜ ์ฝ”๋“œ๋กœ ํ‘œ์‹œ๋œ ์ž‘์—…์„ ๋ฐฑ์—”๋“œ์—์„œ ์ˆ˜ํ–‰ํ•˜๊ณ , ์‹ค์ œ ํ”„๋ก ํŠธ์—”๋“œ์—์„œ๋Š” ํ”„๋ก ํŠธ ์—”๋“œ ์ฝ”๋“œ๋กœ ํ‘œ์‹œ๋œ ์ž‘์—…์„ ์ง„ํ–‰ํ•œ๋‹ค.)
<!DOCTYPE html> <html lang="ko"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>๊ตฌ๊ธ€ OAuth 2.0 ์ธ์ฆ ์˜ˆ์‹œ</title> <script src="https://apis.google.com/js/platform.js"></script> </head> <body> <button id="login-btn">Google ๊ณ„์ •์œผ๋กœ ๋กœ๊ทธ์ธ</button> <div id="user-info" style="display: none"> <p>์•ˆ๋…•, <span id="user-name"></span>!</p> <button id="logout-btn">๋กœ๊ทธ์•„์›ƒ</button> </div> <script src="app.js"></script> </body> </html>
// app.js const CLIENT_ID = "111111111-AAAAAAAAA.apps.googleusercontent.com"; // ํด๋ผ์ด์–ธํŠธ ID const CLIENT_SECRET = '00000-000-AAAAAAAAA_AAAAAAAAA'; // ์‹ค์ œ ๊ตฌํ˜„์‹œ ์„œ๋ฒ„์—์„œ ์•ˆ์ „ํ•˜๊ฒŒ ๊ด€๋ฆฌํ•ด์•ผ ํ•จ const REDIRECT_URI = 'https://example.com'; // ์Šน์ธ๋œ ๋ฆฌ๋””๋ ‰์…˜ URI const scopes = ['profile']; // ๋ฆฌ์†Œ์Šค ๋ฒ”์œ„ // ํ† ํฐ์„ ์ž„์˜๋กœ ๋กœ์ปฌ์— ๋ณด๊ด€ let accessToken = null; let refreshToken = null; // DOM ์š”์†Œ ์ฐธ์กฐ const loginBtn = document.getElementById('login-btn'); const logoutBtn = document.getElementById('logout-btn'); const userInfo = document.getElementById('user-info'); const userName = document.getElementById('user-name'); // ๋กœ๊ทธ์ธ ์ฒ˜๋ฆฌ function signIn() { const authUrl = `https://accounts.google.com/o/oauth2/v2/auth?client_id=${CLIENT_ID}&redirect_uri=${REDIRECT_URI}&scope=${scopes.join(' ')}&response_type=code&access_type=offline&prompt=consent`; window.location.href = authUrl; } // ๋กœ๊ทธ์•„์›ƒ ์ฒ˜๋ฆฌ async function signOut() { accessToken = null; refreshToken = null; updateUI(false); userName.textContent = ''; window.location.href = REDIRECT_URI; } // OAuth ์‘๋‹ต ์ฒ˜๋ฆฌ async function handleAuthResponse() { const urlParams = new URLSearchParams(window.location.search); const code = urlParams.get('code'); if (code) { try { const tokens = await exchangeCodeForTokens(code); accessToken = tokens.access_token; refreshToken = tokens.refresh_token; updateUI(true); fetchUserInfo(); } catch (error) { console.error('์ฝ”๋“œ๋ฅผ ํ† ํฐ์œผ๋กœ ๊ตํ™˜ ์‹คํŒจ:', error); } } } // ์ธ๊ฐ€ ์ฝ”๋“œ๋ฅผ ํ† ํฐ์œผ๋กœ ๊ตํ™˜ async function exchangeCodeForTokens(code) { const response = await fetch('https://oauth2.googleapis.com/token', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, body: new URLSearchParams({ client_id: CLIENT_ID, client_secret: CLIENT_SECRET, redirect_uri: REDIRECT_URI, grant_type: 'authorization_code', code: code, }), }); if (!response.ok) { throw new Error('์ฝ”๋“œ๋ฅผ ํ† ํฐ์œผ๋กœ ๊ตํ™˜ํ•˜๋Š” ๋ฐ ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค.'); } return response.json(); } // ํ† ํฐ ๊ฐฑ์‹  async function refreshAccessToken() { try { const response = await fetch('https://oauth2.googleapis.com/token', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, body: new URLSearchParams({ client_id: CLIENT_ID, client_secret: CLIENT_SECRET, grant_type: 'refresh_token', refresh_token: refreshToken, }), }); if (!response.ok) { throw new Error('์ ‘๊ทผ ํ† ํฐ ๊ฐฑ์‹ ์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค.'); } const data = await response.json(); accessToken = data.access_token; console.log('์ ‘๊ทผ ํ† ํฐ ๊ฐฑ์‹ ํ•จ'); } catch (error) { console.error('์ ‘๊ทผ ํ† ํฐ ๊ฐฑ์‹  ์‹คํŒจ:', error); signOut(); // ๊ฐฑ์‹  ์‹คํŒจ ์‹œ ๋กœ๊ทธ์•„์›ƒ } } // ์‚ฌ์šฉ์ž ์ •๋ณด ๊ฐ€์ ธ์˜ค๊ธฐ async function fetchUserInfo() { try { const response = await fetch( 'https://www.googleapis.com/oauth2/v2/userinfo', { headers: { Authorization: `Bearer ${accessToken}`, }, } ); if (!response.ok) { if (response.status === 401) { // ์ ‘๊ทผ ํ† ํฐ์ด ๋งŒ๋ฃŒ๋œ ๊ฒฝ์šฐ await refreshAccessToken(); // ๊ฐฑ์‹  ํ† ํฐ์œผ๋กœ ์ ‘๊ทผ ํ† ํฐ ๊ฐฑ์‹  return fetchUserInfo(); // ์ƒˆ๋กœ์šด ์ ‘๊ทผ ํ† ํฐ์œผ๋กœ ์žฌ์‹œ๋„ } throw new Error('์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ๋ฐ ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค.'); } const data = await response.json(); displayUserInfo(data); } catch (error) { console.error('์‚ฌ์šฉ์ž ์ •๋ณด ๊ฐ€์ ธ์˜ค๊ธฐ ์‹คํŒจ:', error); } } // UI ์—…๋ฐ์ดํŠธ: ๋กœ๊ทธ์ธ ์—ฌ๋ถ€์— ๋”ฐ๋ฅธ UI ๋ณ€๊ฒฝ function updateUI(isSignedIn) { if (isSignedIn) { loginBtn.style.display = 'none'; userInfo.style.display = 'block'; } else { loginBtn.style.display = 'block'; userInfo.style.display = 'none'; } } // UI ์—…๋ฐ์ดํŠธ: ์‚ฌ์šฉ์ž ์ •๋ณด ํ‘œ์‹œ function displayUserInfo(profile) { userName.textContent = profile.name; } // Google API ํด๋ผ์ด์–ธํŠธ ์ดˆ๊ธฐํ™” function initClient() { loginBtn.addEventListener('click', signIn); logoutBtn.addEventListener('click', signOut); handleAuthResponse(); } window.onload = initClient; // ํด๋ผ์ด์–ธํŠธ ์ดˆ๊ธฐํ™” ์‹คํ–‰