(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 ์ธ์ฆ ํ๋ฆ](https://www.notion.so/image/https%3A%2F%2Fprod-files-secure.s3.us-west-2.amazonaws.com%2F6d7fa230-23c2-4b9c-b287-fdeb076e90bd%2Fda5434bd-2b3a-45b8-918d-29ca3188e003%2FFrame_6-5.png?table=block&id=1061bb7e-371a-80b7-bc74-d2d003021036&cache=v2)
OAuth 2.0 ์ธ์ฆ ํ๋ฆ
- ํด๋ผ์ด์ธํธ ๋ฑ๋ก: ์ธ์ฆ ํ๋ฆ ์ด์ ์ ์ ํ๋ฆฌ์ผ์ด์
์ ์๋น์ค ์ ๊ณต์์ ๋ฑ๋กํ๋ ๋จ๊ณ๋ฅผ ๊ฑฐ์น๊ณ , ์ ํ๋ฆฌ์ผ์ด์
์ ์ ํ์ธ ์ ๋ณด์ธ
client_id
์client_secret
์ ๋ฐ๋๋ค.
- ๊ถํ ์์ฒญ: ์๋น์๊ฐ ์ฌ์ฉ์์๊ฒ ํน์ ๋ฆฌ์์ค์ ๋ํ ์ ๊ทผ ๊ถํ์ ์์ฒญํ๋ค.
- ์ฌ์ฉ์ ์ธ์ฆ: ์ฌ์ฉ์๊ฐ ์ธ๊ฐ ์๋ฒ์ ์ธ์ฆ ํ์ด์ง๋ก ๋ฆฌ๋๋ ํธํ์ฌ ๋ก๊ทธ์ธํ๊ณ ์์ ์ ๊ถํ ์ ๊ณต์ ๋์ํ๋ค. ๋ฆฌ์์ค ์์ ์์ฒญ์๋
client_id
,redirect_uri
,response_type
,scope
๋ฑ์ด ํฌํจ๋๋ค.
- ์ธ๊ฐ ๊ทธ๋ํธ ๋ฐ๊ธ: ์ฌ์ฉ์๊ฐ ๋ฆฌ์์ค ์์์ ์น์ธํ๋ฉด, ์๋น์๋ ์ธ๊ฐ ์๋ฒ๋ก๋ถํฐ ๐์ธ๊ฐ ๊ทธ๋ํธ๋ฅผ ๋ฐ๊ณ
redirect_uri
๋ก ์ฌ์ฉ์๋ฅผ ๋ฆฌ๋๋ ํธํ๋ค.
- ํ ํฐ ์์ฒญ: ์๋น์๋ ์๋ฒ ๊ฐ ํต์ ์ ํตํด ๐์ธ๊ฐ ๊ทธ๋ํธ๋ฅผ ์ธ๊ฐ ์๋ฒ์ ์ ์ํ์ฌ ๐ช์ ๊ทผ ํ ํฐ์ ์์ฒญํ๋ค. ์ด ์์ฒญ์๋
client_id
,client_secret
,์ธ๊ฐ ๊ทธ๋ํธ
๋ฑ์ด ํฌํจ๋๋ค.
- ํ ํฐ ๋ฐ๊ธ: ์ธ๊ฐ ์๋ฒ๋ ์๋น์์ ๐์ธ๊ฐ ๊ทธ๋ํธ๋ฅผ ๊ฒ์ฆํ๊ณ ๐ช์ ๊ทผ ํ ํฐ๊ณผ ๐๊ฐฑ์ ํ ํฐ์ ๋ฐ๊ธํ๋ค.
- ๋ฆฌ์์ค ์ ๊ทผ ์์ฒญ: ์๋น์๋ ์๋น์ค ์ ๊ณต์์ ๐ช์ ๊ทผ ํ ํฐ์ ์ ์ํ๊ณ ๋ณดํธ๋ ๋ฆฌ์์ค์ ์ ๊ทผ์ ์์ฒญํ๋ค.
- ๋ฆฌ์์ค ์ ๊ทผ ํ์ฉ:์๋น์ค ์ ๊ณต์๋ ์ ๊ทผ ํ ํฐ๊ณผ ๋ฆฌ์์ค ์ ๊ทผ ๊ถํ์ ๊ฒ์ฆํ๊ณ , ์ ํจํ ๊ฒฝ์ฐ ๋ฆฌ์์ค ์ ๊ทผ์ ํ์ฉํ๋ค.
- ํ ํฐ ๊ฐฑ์ : ์ก์ธ์ค ํ ํฐ์ด ๋ง๋ฃ๋๋ฉด, ์๋น์๋ ๋ฐฑ๊ทธ๋ผ์ด๋์์ ๐๊ฐฑ์ ํ ํฐ์ ์ฌ์ฉํ์ฌ ์๋ก์ด ์ ๊ทผ ํ ํฐ์ ์์ฒญํ๋ค.
ย
์๋ฅผ ๋ค์ด, ํ ์ ์ ๊ฐ ์๋ก์ด ์ฑ A์ 'Google ๊ณ์ ์ผ๋ก ๋ก๊ทธ์ธ'ํ๋ ๊ฒฝ์ฐ๋ฅผ ์๊ฐํด๋ณด์. ์ด ๊ณผ์ ์์ ๋ก๊ทธ์ธ์ ์๋ํ๋ ์ ์ ๊ฐ
์ฌ์ฉ์
, ์ฑ A๋ ์๋น์
, Google์ด ์๋น์ค ์ ๊ณต์
์ด์ ์ธ๊ฐ ์๋ฒ
๊ฐ ๋๋ค. ์ด๋ฌํ ๊ธฐ๋ฅ์ ๋ณดํต ์๋์ ๊ฐ์ ๊ฐํธ ๋ก๊ทธ์ธ ๋ฉ๋ด๋ฅผ ํตํด ์๋ํ ์ ์๋ค. ![[๊ทธ๋ฆผ 4-3] OAuth์ ์ฌ์ฉํ SNS ๊ฐํธ ๋ก๊ทธ์ธ](https://www.notion.so/image/https%3A%2F%2Fprod-files-secure.s3.us-west-2.amazonaws.com%2F6d7fa230-23c2-4b9c-b287-fdeb076e90bd%2Fd5bae9bb-41f4-4746-b9ea-17a00b52b659%2Fimage.png?table=block&id=1061bb7e-371a-80c8-8be4-e4045221ba2e&cache=v2)
A ์ฑ์ ๊ฐ๋ฐ ๋จ๊ณ์์ Google์ ํด๋ผ์ด์ธํธ๋ก ๋ฑ๋กํ๋ ๋จ๊ณ๋ฅผ ๊ฑฐ์ณค์ ๊ฒ์ด๋ค. ๋ก๊ทธ์ธ ๋ฉ๋ด์์ Google ๋ฒํผ์ ๋๋ฅด๋ฉด A ์ฑ์ ์ฌ์ฉ์๋ฅผ Google์ OAuth ์ธ๊ฐ ์๋ฒ๋ก ๋ฆฌ๋๋ ํธํ๋ค. ์ฌ์ฉ์๋ Google ๋ก๊ทธ์ธ ํ์ด์ง์์ ๊ณ์ ์ ๋ณด๋ฅผ ์ ํํด์ ์ธ์ฆํ๋ค.
ย
![[๊ทธ๋ฆผ 4-4] Google ์ธ์ฆ ํ์ด์ง๋ก ๋ฆฌ๋๋ ํธ](https://www.notion.so/image/https%3A%2F%2Fprod-files-secure.s3.us-west-2.amazonaws.com%2F6d7fa230-23c2-4b9c-b287-fdeb076e90bd%2Fc9829c1e-96ca-41d2-a297-175007469e77%2F8639b29b-cf0b-40d1-9189-0fd85fced74f.png?table=block&id=233f9873-cab6-49bd-9d06-ae079df60c37&cache=v2)
![[๊ทธ๋ฆผ 4-5] Google ๋ก๊ทธ์ธ ํ๋ฉด](https://www.notion.so/image/https%3A%2F%2Fprod-files-secure.s3.us-west-2.amazonaws.com%2F6d7fa230-23c2-4b9c-b287-fdeb076e90bd%2Fab4badf8-cd67-40f5-8523-e177a189b28d%2Fimage.png?table=block&id=158e61ca-6938-4272-a971-e78e96bc914e&cache=v2)
Google์ ์ฌ์ฉ์์๊ฒ A ์ฑ์ด ์์ฒญํ๋ ๊ถํ์ ๋ํด ๋์๋ฅผ ์์ฒญํ๋ค. ์ฌ์ฉ์๊ฐ ๋์ํ๋ฉด Google ์ธ๊ฐ ์๋ฒ๋ A ์ฑ์ ์ฌ์ฉ์์ ์น์ธ์ ๋ํ๋ด๋
์ธ๊ฐ ๊ทธ๋ํธ
๋ฅผ ๋ฐ๊ธํ๋ค. ![[๊ทธ๋ฆผ 4-6] A ์ฑ์ ๋ํ ๊ถํ ๋์ ํ๋ฉด](https://www.notion.so/image/https%3A%2F%2Fprod-files-secure.s3.us-west-2.amazonaws.com%2F6d7fa230-23c2-4b9c-b287-fdeb076e90bd%2F3858eb79-0a18-40ac-b9f7-e1efcf8576f6%2Fimage.png?table=block&id=1061bb7e-371a-8094-8af2-fe8c26373dc0&cache=v2)
์ฌ์ฉ์๋ A ์ฑ์ผ๋ก ๋ฆฌ๋๋ ํธ ๋๋ค. A ์ฑ์ ์๋ฒ ๊ฐ ํต์ ์ ํตํด ์ธ๊ฐ ๊ทธ๋ํธ๋ก Google ์ธ๊ฐ ์๋ฒ์ ์ ๊ทผ ํ ํฐ์ ์์ฒญํ๋ค. ์ธ๊ฐ ์๋ฒ๋ ์ธ๊ฐ ๊ทธ๋ํธ์ ์ ํจ์ฑ์ ๊ฒ์ฆํ๊ณ A ์ฑ์๊ฒ
์ ๊ทผ ํ ํฐ
๊ณผ ๊ฐฑ์ ํ ํฐ
์ ๋ฐ๊ธํ๋ค(์ธ๊ฐ ๊ทธ๋ํธ โ ํ ํฐ). A ์ฑ์ ์ ๊ทผ ํ ํฐ์ ์ฌ์ฉํด Google์๊ฒ ์ฌ์ฉ์์ ๊ณ์ ์ ๋ณด ์ผ๋ถ๋ฅผ ์์ฒญํ๋ค. Google์ ์ ๊ทผ ํ ํฐ์ ์ ํจ์ฑ๊ณผ ๋ฆฌ์์ค ๊ถํ์ ๊ฒ์ฆํ ๋ค, ์์ฒญ๋ ์ฌ์ฉ์ ๊ณ์ ์ ๋ณด๋ฅผ A ์ฑ์๊ฒ ์ ๊ณตํ๋ค(์ ๊ทผ ํ ํฐ โ ๊ณ์ ์ ๋ณด). A ์ฑ์ Google์ ์ ๋ณด๋ฅผ ๋ฐํ์ผ๋ก ์ฌ์ฉ์์ ๋ก๊ทธ์ธ ํ๋ก์ธ์ค๋ฅผ ์๋ฃํ๋ค. ์ดํ ์ ๊ทผ ํ ํฐ์ด ๋ง๋ฃ๋๋ฉด, A ์ฑ์ ๋ฐฑ๊ทธ๋ผ์ด๋์์ ๊ฐฑ์ ํ ํฐ์ ์ฌ์ฉํ์ฌ ์๋ก์ด ์ ๊ทผ ํ ํฐ์ ์์ฒญํ๋ค(๊ฐฑ์ ํ ํฐ โ ์ ๊ทผ ํ ํฐ). ์ด๋ฅผ ํตํด ์ฌ์ฉ์๋ ๋ค์ ๋ก๊ทธ์ธํ ํ์ ์์ด ์ง์์ ์ผ๋ก ์๋น์ค๋ฅผ ์ด์ฉํ ์ ์๋ค.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์ ์ธ์ฆ ๊ณผ์ ์๋ ์ธ์ฆ ์๋ฒ๋ก์ ๋ฆฌ๋๋ ์
์ด ํฌํจ๋๋ค. ์์ ์ฌ๋ฌ ๋ณด์ ์ํ์์ ์ดํด๋ณธ ๊ฒ์ฒ๋ผ, ๋ฆฌ๋๋ ํธ ๊ธฐ๋ฅ์ ๋ณด์ ์ํ์ ์ด๋ํ ์ ์๊ธฐ ๋๋ฌธ์ ์ ์ ํ ๋ณด์ ์ฒ๋ฆฌ๊ฐ ํ์ํ๋ค.
- ๋ฆฌ๋๋ ํธ URI ๊ฒ์ฆ
๋์ ์ผ๋ก ์์ฑ๋ ๋ฆฌ๋๋ ํธ URI๋ ๋ณด์ ์ํ์ด ์์ผ๋ฏ๋ก ์ ์ ๋ฆฌ๋๋ ํธ URI๋ง์ ์ฌ์ฉํด์ผ ํ๋ค. 'Google ๊ณ์ ์ผ๋ก ๋ก๊ทธ์ธ' ์์์์๋ ๋ฏธ๋ฆฌ ๋ฑ๋กํด ๋
redirect_uri
๋ก ๋ฆฌ๋๋ ํธ๋๋ค.- ์ํ(State) ๋งค๊ฐ๋ณ์ ์ฌ์ฉ
CSRF ๊ณต๊ฒฉ์ ๋ฐฉ์งํ๊ธฐ ์ํด ์ํ(state) ๋งค๊ฐ๋ณ์ ์ฌ์ฉ์ ๊ณ ๋ คํ ์ ์๋ค. ์ํ ๋งค๊ฐ๋ณ์๋ ๋๋คํ๊ฒ ์์ฑ๋ ๋ฌธ์์ด๋ก CSRF ํ ํฐ๊ณผ ์ ์ฌํ ์ญํ ์ ํ๋ค.
์ด ๊ฐ์ ํด๋ผ์ด์ธํธ ์์ฒญ ์ ์์ฑ๋์ด ์์ฒญ์ ํฌํจ๋๊ณ , ์ธ์ฆ ์๋ฒ๋ ์ฌ์ฉ์ ์ธ์ฆ ์๋ฃ ํ ์๋ต์ ์ด ๋งค๊ฐ๋ณ์๋ฅผ ํฌํจํ์ฌ ์ ๋ฌํ๋ค. ํด๋ผ์ด์ธํธ๋ ์์ฒญ๊ณผ ์๋ต์ ์ํ ๋งค๊ฐ๋ณ์๋ฅผ ๋น๊ตํ์ฌ ๋์ผ ์ธ์
์ฌ๋ถ๋ฅผ ๊ฒ์ฆํ๋ค.
2) ๊ทธ๋ํธ ์ ํ(Grant Types)
์ฌ์ฉ์์ ์น์ธ์ ๋ํ๋ด๋ ์ธ๊ฐ ๊ทธ๋ํธ์๋ ์ฌ๋ฌ ์ข
๋ฅ๊ฐ ์์ด์ ๋ค์ํ ์ธ์ฆ ์๋๋ฆฌ์ค์ ๋์ํ ์ ์๋ค. ํ๋ก ํธ์๋๋ ์๋น์ค ์ ๊ณต์๊ฐ ์ง์ํ๋ ๊ทธ๋ํธ ์ ํ์ ์ดํดํ๊ณ ๊ทธ์ ๋ง๋ ์ธ์ฆ ํ๋ฆ์ ๊ตฌํํด์ผ ํ๋ค. ์ฃผ์ ๊ทธ๋ํธ ์ ํ๋ค์ ๋ค์๊ณผ ๊ฐ๋ค.
- ์ธ์ฆ ์ฝ๋ ๊ทธ๋ํธ
ํด๋ผ์ด์ธํธ๊ฐ ๊ทธ๋ํธ๋ก ์ธ์ฆ ์ฝ๋๋ฅผ ๋ฐ๊ณ , ์ด๋ฅผ ํตํด ์ ๊ทผ ํ ํฐ์ ์ป๋ ๋ฐฉ์. ๊ฐ์ฅ ๋ํ์ ์ธ ๊ทธ๋ํธ ์ ํ์ผ๋ก ๋น๊ต์ ๋ ์์ ํ๋ค. ์์ 'Google ๊ณ์ ์ผ๋ก ๋ก๊ทธ์ธ' ์์์์ ์ฌ์ฉํ ๋ฐฉ๋ฒ์ด ์ด๊ฒ์ด๋ค.
- ์๋ฌต์ ๊ทธ๋ํธ
ํด๋ผ์ด์ธํธ๊ฐ ์ธ์ฆ ์ฝ๋ ์์ด ์ง์ ์ ๊ทผ ํ ํฐ์ ์ป๋ ๋ฐฉ์. ๋ก์ง์ด ๊ฐ๋จํ์ง๋ง ์๋์ ์ผ๋ก ๋ณด์์ฑ์ด ๋ฎ๋ค.
- PKCE(Proof Key for Code Exchange)
์ธ์ฆ ์ฝ๋ ๊ทธ๋ํธ์ ๋ณด์ ๊ฐํ ๋ฒ์ ์ผ๋ก, ํด๋ผ์ด์ธํธ๊ฐ ์ฝ๋๋ฅผ ๊ตํํ ๋ ์ถ๊ฐ์ ์ธ ๋ณด์ ๊ณ์ธต์ ์ ๊ณตํ๋ ๋ฐฉ์
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 ๋ฐ๊ธ
- https://console.cloud.google.com/์ ์ ์ํด์ ์ ํ๋ก์ ํธ๋ฅผ ์์ฑํ๊ฑฐ๋ ๊ธฐ์กด ํ๋ก์ ํธ๋ฅผ ์ ํํจ(๋ก๊ณ ์ฐ์ธก ์ ํ๊ธฐ์์ ํ๋ก์ ํธ ์ ํ ๊ฐ๋ฅ)

- ์ข์๋จ ๋ฉ๋ด ์์ด์ฝ์ ๋๋ฅด๊ณ
API ๋ฐ ์๋น์ค
>์ฌ์ฉ์ ์ธ์ฆ ์ ๋ณด
๋ก ์ด๋

์ฌ์ฉ์ ์ธ์ฆ ์ ๋ณด ๋ง๋ค๊ธฐ
>OAuth ํด๋ผ์ด์ธํธ ID
๋ฅผ ์ ํ

๋์ ํ๋ฉด ๊ตฌ์ฑ
>User Type: ์ธ๋ถ
>๋ง๋ค๊ธฐ
๋ฅผ ์ ํํ๊ณ , ์ฑ ์ด๋ฆ/์ด๋ฉ์ผ/๊ฐ๋ฐ์ ์ฐ๋ฝ์ฒ ๋ฑ์ ์ ๋ ฅํ ๋ค์ ์ฅ
๋ฒ์ ์ถ๊ฐ ๋๋ ์ญ์
๋ฅผ ๋๋ฌ '../auth/userinfo.profile' ๋ฑ ์ํ๋ ์ ๋ณด ๋ฒ์๋ฅผ ์ ํํ๊ณ์ ๋ฐ์ดํธ
์์ ์ฅ

- ํ์ํ ๊ฒฝ์ฐ ํ
์คํธ ์ฌ์ฉ์๋ฅผ ์ถ๊ฐํ๊ณ
์ ์ฅ
ํ ๋ค ์์ฝ ์ ๋ณด ํ์ธ
์ฌ์ฉ์ ์ธ์ฆ ์ ๋ณด ๋ง๋ค๊ธฐ
>OAuth ํด๋ผ์ด์ธํธ ID
๋ก ๋์๊ฐ์ ์ ํ๋ฆฌ์ผ์ด์ ์ ํ์ผ๋ก์น ์ ํ๋ฆฌ์ผ์ด์
์ ํ ํ ์ฑ ์ด๋ฆ ์ ๋ ฅ

- '์น์ธ๋ JavaScript ์๋ณธ'์ ์น ์๋น์ค ์ฃผ์, '์น์ธ๋ ๋ฆฌ๋๋ ์
URI'์ ๋ก๊ทธ์ธ ํ ๋ฆฌ๋๋ ํธํ ์ฃผ์๋ฅผ ์
๋ ฅ ํ
๋ง๋ค๊ธฐ
์ ํ


- '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; // ํด๋ผ์ด์ธํธ ์ด๊ธฐํ ์คํ