๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
๐ŸŒฑBackend

SNS ๋กœ๊ทธ์ธ - Spring OAuth2 Client

by discphy 2023. 7. 24.
๋ฐ˜์‘ํ˜•

SNS ๋กœ๊ทธ์ธ - Spring OAuth2 Client

OAuth2๋ž€?

๊ฐœ๋…


  • OAuth(Open Authorization) : ์ธํ„ฐ๋„ท ์‚ฌ์šฉ์ž๋“ค์ด ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์ œ๊ณตํ•˜์ง€ ์•Š๊ณ , ๋‹ค๋ฅธ ์›น์‚ฌ์ดํŠธ ์ƒ์˜ ์ž์‹ ๋“ค์˜ ์ •๋ณด์— ๋Œ€ํ•ด ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์ ‘๊ทผ ๊ถŒํ•œ์„ ๋ถ€์—ฌํ•  ์ˆ˜ ์žˆ๋Š” ๊ณตํ†ต์ ์ธ ์ˆ˜๋‹จ์œผ๋กœ์จ ์‚ฌ์šฉ๋˜๋Š” ์œ„์ž„ ๊ถŒํ•œ๋ถ€์—ฌ๋ฅผ ์œ„ํ•œ ํ‘œ์ค€ ํ”„๋กœํ† ์ฝœ์ด๋‹ค.

๐Ÿ’ก `OAuth2`๋Š” `OAuth`์˜ ์•Œ๋ ค์ง„ ๋ณด์•ˆ ๋ฌธ์ œ ๋“ฑ์„ ๊ฐœ์„ ํ•œ ๋ฒ„์ „์ž„

์ฃผ์š” ์šฉ์–ด


์ด๋ฆ„ ์„ค๋ช…
Authentication (์ธ์ฆ) ์ ‘๊ทผ ์ž๊ฒฉ์ด ์žˆ๋Š”์ง€ ๊ฒ€์ฆํ•˜๋Š” ๋‹จ๊ณ„
Authorization (์ธ๊ฐ€) ์ž์›์— ์ ‘๊ทผํ•  ๊ถŒํ•œ์„ ๋ถ€์—ฌํ•˜๋Š” ๊ฒƒ์ด๋ฉฐ, ์ธ๊ฐ€๊ฐ€ ์™„๋ฃŒ๋˜๋ฉด ๋ฆฌ์†Œ์Šค ์ ‘๊ทผ ๊ถŒํ•œ์ด ๋‹ด๊ธด Access Token์ด ํด๋ผ์ด์–ธํŠธ์—๊ฒŒ ๋ถ€์—ฌ
Access Token ๋ฆฌ์†Œ์Šค ์„œ๋ฒ„์—๊ฒŒ์„œ ๋ฆฌ์†Œ์Šค ์†Œ์œ ์ž์˜ ๋ณดํ˜ธ๋œ ์ž์›์„ ํš๋“ํ•  ๋•Œ ์‚ฌ์šฉํ•˜๋Š” ํ† ํฐ
Refresh Token Access Token๋งŒ๋ฃŒ ์‹œ ์ด๋ฅผ ๊ฐฑ์‹ ํ•˜๊ธฐ ์œ„ํ•œ ์šฉ๋„๋กœ ์‚ฌ์šฉํ•˜๋Š” ํ† ํฐ

๊ตฌ์„ฑ


์ด๋ฆ„ ์„ค๋ช…
Resource Owner ์›น ์„œ๋น„์Šค๋ฅผ ์ด์šฉํ•˜๋ ค๋Š” ์œ ์ €, ์ž์›(๊ฐœ์ธ์ •๋ณด)์„ ์†Œ์œ ํ•˜๋Š” ์ž, ์‚ฌ์šฉ์ž
Client ์ž์‚ฌ ๋˜๋Š” ๊ฐœ์ธ์ด ๋งŒ๋“  ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์„œ๋ฒ„
Resource Server ์‚ฌ์šฉ์ž์˜ ๊ฐœ์ธ์ •๋ณด๋ฅผ ๊ฐ€์ง€๊ณ ์žˆ๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ (Google, Facebook, Kakao ๋“ฑ) ํšŒ์‚ฌ ์„œ๋ฒ„
  • Client๋Š” Token์„ ์ด ์„œ๋ฒ„๋กœ ๋„˜๊ฒจ ๊ฐœ์ธ์ •๋ณด๋ฅผ ์‘๋‹ต ๋ฐ›์„ ์ˆ˜ ์žˆ์Œ |
    | Authorization Server | ๊ถŒํ•œ์„ ๋ถ€์—ฌ(์ธ์ฆ์— ์‚ฌ์šฉํ•  ์•„์ดํ…œ์„ ์ œ๊ณต์ฃผ๋Š”)ํ•ด์ฃผ๋Š” ์„œ๋ฒ„
  • ์‚ฌ์šฉ์ž๋Š” ์ด ์„œ๋ฒ„๋กœ ID, PW๋ฅผ ๋„˜๊ฒจ Authorization Code๋ฅผ ๋ฐœ๊ธ‰ ๋ฐ›์„ ์ˆ˜ ์žˆ์Œ
  • Client๋Š” ์ด ์„œ๋ฒ„๋กœ Authorization Code์„ ๋„˜๊ฒจ Token์„ ๋ฐ›๊ธ‰ ๋ฐ›์„ ์ˆ˜ ์žˆ์Œ |

์ธ์ฆ ๋ฐฉ์‹


  • Authorization Code Grant(๊ถŒํ•œ ๋ถ€์—ฌ ์ฝ”๋“œ ์Šน์ธ ๋ฐฉ์‹) : OAuth2์—์„œ ๊ฐ€์žฅ ๊ธฐ๋ณธ์ด ๋˜๋Š” ๋ฐฉ์‹์ด๋ฉฐ, SNS ๋กœ๊ทธ์ธ ๊ธฐ๋Šฅ์—์„œ ์‚ฌ์šฉ๋˜๋Š” ๋ฐฉ์‹

    1. ์ ‘๊ทผ ๊ถŒํ•œ ์š”์ฒญ ์‹œ, response_type=code๋กœ ์š”์ฒญํ•˜๊ฒŒ ๋˜๋ฉด ํด๋ผ์ด์–ธํŠธ๋Š” Authorization Server์—์„œ ์ œ๊ณตํ•˜๋Š” ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€ ์ด๋™
    2. ๋กœ๊ทธ์ธ ์‹œ, Authorization Server๋Š” ์ ‘๊ทผ ๊ถŒํ•œ ์š”์ฒญ์‹œ์— ๋ฐ›์€ redirect_url๋กœ Authorization Code๋ฅผ ์ „๋‹ฌ
    3. Client์—์„œ ์ „๋‹ฌ๋ฐ›์€ Authorization Code๋กœ Access Token์š”์ฒญ
    4. Client์—์„œ ์ „๋‹ฌ๋ฐ›์€ Access Token์œผ๋กœ Resource Server์— ์ž์› ์š”์ฒญ

    ์ด์™ธ์—๋„ ๋‹ค๋ฅธ ๋ฐฉ์‹์ด ์กด์žฌํ•˜์ง€๋งŒ ์—ฌ๊ธฐ์„œ ์„ค๋ช…ํ•˜์ง€ ์•Š๊ฒ ๋‹ค.

    • Implicit Grant : ์•”๋ฌต์  ์Šน์ธ ๋ฐฉ์‹
    • Client Credentials Grant : ํด๋ผ์ด์–ธํŠธ ์ž๊ฒฉ ์ฆ๋ช… ๋ฐฉ์‹
    • Resource Owner Password Credentials Grant : ์ž์› ์†Œ์œ ์ž ์ž๊ฒฉ ์ฆ๋ช… ๋ฐฉ์‹

    [์ฐธ๊ณ ] : https://wildeveloperetrain.tistory.com/247

  • ![<Authorization Code Grant ํ”Œ๋กœ์šฐ>]

Spring Security ์›๋ฆฌ

DelegatingProxyChain


<<code>DelegatingProxy</code>์˜ ์—ญํ• >

gt;

<DelegatingProxy์˜ ์—ญํ• ></

  • ์„œ๋ธ”๋ฆฟ ํ•„ํ„ฐ๋Š” ์„œ๋ธ”๋ฆฟ ์ปจํ…Œ์ด๋„ˆ์—์„œ ๊ด€๋ฆฌ๋˜์–ด ์Šคํ”„๋ง ๋นˆ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†๋‹ค.
  • DelegatingFilterProxy : ์„œ๋ธ”๋ฆฟ ํ•„ํ„ฐ์™€ ์Šคํ”„๋ง ๋นˆ์„ ์—ฐ๊ฒฐํ•ด์ฃผ๋Š” ํด๋ž˜์Šค, ์„œ๋ธ”๋ฆฟ ํ•„ํ„ฐ๋กœ ์š”์ฒญ์„ ๋ฐ›์•„์„œ ์Šคํ”„๋ง์—์„œ ๊ด€๋ฆฌํ•˜๋Š” ํ•„ํ„ฐ์—๊ฒŒ ์š”์ฒญ์„ ์œ„์ž„ํ•˜๋Š” ์—ญํ• ์„ ํ•œ๋‹ค.
  • springSecurityFilterChain : ์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ ์Šคํ”„๋ง ๋นˆ

FilterChainProxy


<์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ ๊ธฐ๋ณธ ํ•„ํ„ฐ ๋ชฉ๋ก ๋ฐ ์ˆœ์„œ>

gt;

<์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ ๊ธฐ๋ณธ ํ•„ํ„ฐ ๋ชฉ๋ก ๋ฐ ์ˆœ์„œ></์Šคํ”„๋ง>

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

OAuth2 ๋กœ๊ทธ์ธ์„ ํ™œ์„ฑํ™” ํ•˜๋ฉด UsernamePasswordAuthenticationFilter ๋Œ€์‹  OAuth2LoginAuthenticationFilter ํ•„ํ„ฐ๊ฐ€ ์‚ฌ์šฉ ๋œ๋‹ค.

๋™์ž‘ ๋ฐฉ์‹


<์„œ๋ธ”๋ฆฟ ์ปจํ…Œ์ด๋„ˆ์™€ ์Šคํ”„๋ง ์ปจํ…Œ์ด๋„ˆ์˜ <code>DelegatingFilterProxy</code>์— ๋Œ€ํ•œ <code>Flow</code>>

gt;

<์„œ๋ธ”๋ฆฟ ์ปจํ…Œ์ด๋„ˆ์™€ ์Šคํ”„๋ง ์ปจํ…Œ์ด๋„ˆ์˜ DelegatingFilterProxy์— ๋Œ€ํ•œ `Flow`></์„œ๋ธ”๋ฆฟ>

  • DelegatingFilterProxy์ด ์š”์ฒญ์„ ๋ฐ›๊ฒŒ๋˜๋ฉด delegate request๋กœ ์š”์ฒญ ์œ„์ž„
  • FilterChainProxy์˜ ํ•„ํ„ฐ ๋ชฉ๋ก๋“ค ์ˆœ์ฐจ์ ์œผ๋กœ ์ˆ˜ํ–‰
  • ํ•„ํ„ฐ ์™„๋ฃŒ ์‹œ DispatcherServlet(Controller)๋กœ ์ „๋‹ฌ

Spring OAuth2 Client ์›๋ฆฌ

Access Token ํš๋“


<์‚ฌ์šฉ์ž ๋กœ๊ทธ์ธ ํ›„ <code>Access Token</code>์„ ๋ฐœ๊ธ‰ ๋ฐ›๋Š” <code>Flow</code>>

gt;

<์‚ฌ์šฉ์ž ๋กœ๊ทธ์ธ ํ›„ Access Token์„ ๋ฐœ๊ธ‰ ๋ฐ›๋Š” `Flow`></์‚ฌ์šฉ์ž>

  • Auth-Server์—์„œ ๋กœ๊ทธ์ธ์„ ์™„๋ฃŒ ํ•˜๋ฉด ์„ค์ •ํ•œ Redirect URL๋กœ Authorization Code๋ฅผ ์ „๋‹ฌ
  • Authorization Code๋ฅผ ๊ฐ€์ง€๊ณ  Access Token ์š”์ฒญ
  • Access Token ๋ฐœ๊ธ‰

User Info ํš๋“


<๋ฐœ๊ธ‰ ๋ฐ›์€ <code>Access Token</code>์œผ๋กœ ์‚ฌ์šฉ์ž ๋ฆฌ์†Œ์Šค ์ •๋ณด๋ฅผ ์กฐํšŒํ•˜์—ฌ ์ธ์ฆ ์ „์—ญ ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•˜๋Š” <code>Flow</code>>

gt;

<๋ฐœ๊ธ‰ ๋ฐ›์€ Access Token์œผ๋กœ ์‚ฌ์šฉ์ž ๋ฆฌ์†Œ์Šค ์ •๋ณด๋ฅผ ์กฐํšŒํ•˜์—ฌ ์ธ์ฆ ์ „์—ญ ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•˜๋Š” `Flow`></๋ฐœ๊ธ‰>

Spring OAuth2 Client ์‹ค์ „ ์˜ˆ์ œ

๐Ÿ“– ๊ฐ€์žฅ ๋งŽ์ด ์‚ฌ์šฉํ•˜๋Š” SNS ํ”Œ๋žซํผ ์ค‘ ์Šคํ”„๋ง ๋ถ€ํŠธ์—์„œ ๊ธฐ๋ณธ ์ œ๊ณตํ•ด์ฃผ๋Š” `Google`, `Facebook`, `Github`์™€ ์ด ์™ธ์˜ ์ง์ ‘ `Provider` ์„ค์ • ์ž‘์—…์ด ํ•„์š”ํ•œ `Kakao`, `Naver`์˜ SNS ๋กœ๊ทธ์ธ ๊ธฐ๋Šฅ์„ ์˜ˆ์ œ๋กœ ํ™•์ธํ•ด๋ณด์ž.

ํ”„๋กœ์ ํŠธ ์ƒ์„ฑ


  1. https://start.spring.io/ ์—์„œ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์˜ˆ์ œ ํ”„๋กœ์ ํŠธ๋ฅผ ์„ธํŒ…

  2. โ€˜GENERATEโ€™๋ฅผ ํด๋ฆญํ•˜์—ฌ ํ”„๋กœ์ ํŠธ ์ €์žฅ ****<์Šคํ”„๋ง ๋ถ€ํŠธ ํ”„๋กœ์ ํŠธ ์ƒ์„ฑ ํŽ˜์ด์ง€์—์„œ ์˜ˆ์ œ ์„ค์ •ํ•˜๋Š” ํ™”๋ฉด></์Šคํ”„๋ง>

  3. <์Šคํ”„๋ง ๋ถ€ํŠธ ํ”„๋กœ์ ํŠธ ์ƒ์„ฑ ํŽ˜์ด์ง€์—์„œ ์˜ˆ์ œ ํ”„๋กœ์ ํŠธ ์„ค์ •ํ•˜๋Š” ํ™”๋ฉด>

    gt;

  4. IDE๋ฅผ ์ด์šฉํ•˜์—ฌ WAS๋ฅผ ์‹คํ–‰

  5. ๋‹ค์Œ๊ณผ ๊ฐ™์€ ํ™”๋ฉด ํŽ˜์ด์ง€๊ฐ€ ํ™•์ธ๋˜๋ฉด ํ”„๋กœ์ ํŠธ ์„ค์ • ์™„๋ฃŒ<localhost:8080 ์ ‘์† ์‹œ ๋…ธ์ถœ๋˜๋Š” ํ™”๋ฉด></

  6. <<code>localhost:8080</code> ์ ‘์† ์‹œ ๋…ธ์ถœ๋˜๋Š” ํ™”๋ฉด>

    gt;

๋กœ์ปฌ ํ˜ธ์ŠคํŠธ ์„ค์ •


๐Ÿšจ SNS ํ”Œ๋žซํผ์—์„œ `redirect url`์„ ์„ค์ •ํ•  ๋•Œ `https` ํ”„๋กœํ† ์ฝœ๋งŒ ์ง€์›ํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋Œ€๋ถ€๋ถ„์ด๋‹ค. `ngrok`์ด๋ผ๋Š” ์˜คํ”ˆ ํ„ฐ๋„๋ง ํ”„๋กœ๊ทธ๋žจ์„ ์‚ฌ์šฉํ•˜์—ฌ `public https url`์„ ์„ธํŒ…ํ•˜๋ฉด ๊ฐ€๋Šฅํ•˜๋‹ค.

  • ngrok ๊ณต์‹ ํ™ˆํŽ˜์ด์ง€ : https://ngrok.com/

  • ์„ค์น˜ ๊ฐ€์ด๋“œ : https://tlog.tammolo.com/posts/ngrok-localtunnel

  • ๋กœ์ปฌ WAS ์‹คํ–‰ ํ›„, ํ„ฐ๋ฏธ๋„ ์•„๋ž˜ ๋ช…๋ น์–ด ์ž…๋ ฅ

  • $ ngrok http 8080

  • ๋‹ค์Œ๊ณผ ๊ฐ™์ด https://c029-218-152-213-155.ngrok-free.app ๋กœ ํ„ฐ๋„๋ง์ด ์™„๋ฃŒ๋œ ๊ฒƒ์„ ํ™•์ธ<ngrok ์‹คํ–‰ ํ™”๋ฉด></

  • <<code>ngrok</code> ์‹คํ–‰ ํ™”๋ฉด>

    gt;

Untitled

  • ์œ„์˜ URL๋กœ ์ ‘์† ์‹œ, ๋‹ค์Œ๊ณผ ๊ฐ™์€ ํ™”๋ฉด ๋…ธ์ถœ ํ™•์ธ<ngrok ํ„ฐ๋„๋ง URL ์ ‘์† ํ™”๋ฉด></

  • <<code>ngrok</code> ํ„ฐ๋„๋ง URL ์ ‘์† ํ™”๋ฉด>

    gt;

SNS ํ”Œ๋žซํผ ์„ค์ •


๐Ÿ’ก ์Šคํ”„๋ง ๊ณต์‹ ํ™ˆํŽ˜์ด์ง€์—์„œ ๊ธฐ๋ณธ์œผ๋กœ ์ œ๊ณตํ•˜๋Š” Redirect URI ํ…œํ”Œ๋ฆฟ์€ `{baseUrl}/login/oauth2/code/{registrationId}`์ด๋‹ค.

The default redirect URI template is {baseUrl}/login/oauth2/code/{registrationId}.  The registrationId is a unique identifier for the ClientRegistration.  [์ฐธ๊ณ ] : [https://spring.io/guides/tutorials/spring-boot-oauth2/](https://spring.io/guides/tutorials/spring-boot-oauth2/)

CommonOAuth2Provider


  • Spring OAuth2 Client์—์„œ ๊ธฐ๋ณธ์œผ๋กœ ์ œ๊ณตํ•˜๋Š” ํ”Œ๋žซํผ์˜ ์ •๋ณด๊ฐ’์„ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค.
public enum CommonOAuth2Provider {      GOOGLE {          @Override         public Builder getBuilder(String registrationId) {             ClientRegistration.Builder builder = getBuilder(registrationId,                     ClientAuthenticationMethod.CLIENT_SECRET_BASIC, DEFAULT_REDIRECT_URL);             builder.scope("openid", "profile", "email");             builder.authorizationUri("https://accounts.google.com/o/oauth2/v2/auth");             builder.tokenUri("https://www.googleapis.com/oauth2/v4/token");             builder.jwkSetUri("https://www.googleapis.com/oauth2/v3/certs");             builder.issuerUri("https://accounts.google.com");             builder.userInfoUri("https://www.googleapis.com/oauth2/v3/userinfo");             builder.userNameAttributeName(IdTokenClaimNames.SUB);             builder.clientName("Google");             return builder;         }      },      GITHUB {          @Override         public Builder getBuilder(String registrationId) {             ClientRegistration.Builder builder = getBuilder(registrationId,                     ClientAuthenticationMethod.CLIENT_SECRET_BASIC, DEFAULT_REDIRECT_URL);             builder.scope("read:user");             builder.authorizationUri("https://github.com/login/oauth/authorize");             builder.tokenUri("https://github.com/login/oauth/access_token");             builder.userInfoUri("https://api.github.com/user");             builder.userNameAttributeName("id");             builder.clientName("GitHub");             return builder;         }      },      FACEBOOK {          @Override         public Builder getBuilder(String registrationId) {             ClientRegistration.Builder builder = getBuilder(registrationId,                     ClientAuthenticationMethod.CLIENT_SECRET_POST, DEFAULT_REDIRECT_URL);             builder.scope("public_profile", "email");             builder.authorizationUri("https://www.facebook.com/v2.8/dialog/oauth");             builder.tokenUri("https://graph.facebook.com/v2.8/oauth/access_token");             builder.userInfoUri("https://graph.facebook.com/me?fields=id,name,email");             builder.userNameAttributeName("id");             builder.clientName("Facebook");             return builder;         }      }     ... }

application.yml ์„ค์ •


spring:    security:       oauth2:          client:             registration:                google:                                     client-id: #{client-id}                   client-secret: #{client-secret}                   redirect-uri: "https://{baseHost}{basePort}/login/oauth2/code/{registrationId}"                facebook:                   client-id: #{client-id}                   client-secret: #{client-secret}                   redirect-uri: "https://{baseHost}{basePort}/login/oauth2/code/{registrationId}"                github:                   client-id: #{client-id}                   client-secret: #{client-secret}                   redirect-uri: "https://{baseHost}{basePort}/login/oauth2/code/{registrationId}"                naver:                   client-name: Naver                   client-id: #{client-id}                   client-secret: #{client-secret}                   authorization-grant-type: authorization_code                   redirect-uri: "https://{baseHost}{basePort}/login/oauth2/code/{registrationId}"                   scope: name,email,age                kakao:                   client-name: Kakao                   client-id: #{client-id}                   client-secret: #{client-secret}                   authorization-grant-type: authorization_code                   redirect-uri: "https://{baseHost}{basePort}/login/oauth2/code/{registrationId}"                   scope: profile_nickname,account_email                   client-authentication-method: post             provider: # ๊ธฐ๋ณธ์ œ๊ณตํ•˜์ง€ ์•Š๋Š” ํ”Œ๋žซํผ์ธ ๊ฒฝ์šฐ, ์ง์ ‘ Provider ์„ค์ • ํ•„์š”                naver:                   authorization_uri: https://nid.naver.com/oauth2.0/authorize                   token_uri: https://nid.naver.com/oauth2.0/token                   user-info-uri: https://openapi.naver.com/v1/nid/me                   user_name_attribute: response                kakao:                   authorization_uri: https://kauth.kakao.com/oauth/authorize                   token_uri: https://kauth.kakao.com/oauth/token                   user-info-uri: https://kapi.kakao.com/v2/user/me                   user_name_attribute: id
  • #{client-id}, #{client-secret}์—๋Š” ํ”Œ๋žซํผ ์„ค์ •์˜ ๋ณด์•ˆ ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์™€์•ผ ํ•œ๋‹ค.
  • Naver, Kakao์˜ ๊ฒฝ์šฐ ๊ธฐ๋ณธ์œผ๋กœ ์ œ๊ณตํ•˜์ง€ ์•Š๋Š” ํ”Œ๋žซํผ์œผ๋กœ ์ง์ ‘ provider์„ ์„ค์ •ํ•ด์•ผํ•œ๋‹ค.
  • authorization-grant-type : ์ธ์ฆ ๋ฐฉ์‹์€ authorization_code๋กœ ์„ค์ •
  • scope : ๊ฐ ํ”Œ๋žซํผ ๋ณ„, ํด๋ผ์ด์–ธํŠธ์—๊ฒŒ ํ—ˆ์šฉ๋œ ๋ฆฌ์†Œ์Šค์˜ ๋™์˜ ํ•ญ๋ชฉ๋งŒ ๋ช…์‹œํ•˜๋ฉด ๋œ๋‹ค.

Spring Security ์„ค์ • ๋ฐ ํ™œ์„ฑํ™”


@Configuration @EnableWebSecurity public class SecurityConfig {      @Bean     public SecurityFilterChain config(HttpSecurity http) throws Exception {         return http                 .authorizeRequests()                     .antMatchers("/login").permitAll()                     .anyRequest().authenticated()                     .and()                 .oauth2Login()                     .defaultSuccessUrl("/user")                     .and()                 .build();     } }
  • /login๋งŒ ์ „์ฒด ํ—ˆ์šฉ, /login์„ ์ œ์™ธํ•œ ๋‚˜๋จธ์ง€ path๋Š” ์ธ์ฆ ๊ณผ์ •(์ฆ‰, ๋กœ๊ทธ์ธ)์ด ํ•„์ˆ˜
  • oauth2Login() : oauth2๋ฅผ ํ™œ์„ฑํ™”
  • .defaultSuccessUrl("/user"): ๋กœ๊ทธ์ธ ์„ฑ๊ณต์‹œ์˜ /user๋กœ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ ํ•œ๋‹ค.

/login


  • /login์„ ๋”ฐ๋กœ ๊ตฌํ˜„ํ•˜์ง€ ์•Š์•˜์„ ์‹œ, ์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ์—์„œ ๊ธฐ๋ณธ ์ œ๊ณต๋˜๋Š” ui๋ฅผ ๊ทธ๋ ค์ค€๋‹ค.
public class DefaultLoginPageGeneratingFilter extends GenericFilterBean {     private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {         boolean loginError = isErrorPage(request);         boolean logoutSuccess = isLogoutSuccess(request);         if (isLoginUrlRequest(request) || loginError || logoutSuccess) {             String loginPageHtml = generateLoginPageHtml(request, loginError, logoutSuccess);             response.setContentType("text/html;charset=UTF-8");             response.setContentLength(loginPageHtml.getBytes(StandardCharsets.UTF_8).length);             response.getWriter().write(loginPageHtml);             return;         }         chain.doFilter(request, response);     }      private String generateLoginPageHtml(HttpServletRequest request, boolean loginError, boolean logoutSuccess) {         ...         if (this.oauth2LoginEnabled) { // oauth2Login์ด ํ™œ์„ฑํ™” ์ผ ๋•Œ             sb.append("<h2 class=\"form-signin-heading\">Login with OAuth 2.0</h2>");             sb.append(createError(loginError, errorMsg));             sb.append(createLogoutSuccess(logoutSuccess));             sb.append("<table class=\"table table-striped\">\n");             for (Map.Entry<String, String> clientAuthenticationUrlToClientName : this.oauth2AuthenticationUrlToClientName                     .entrySet()) {                 sb.append(" <tr><td>");                 String url = clientAuthenticationUrlToClientName.getKey();                 sb.append("<a href=\"").append(contextPath).append(url).append("\">");                 String clientName = HtmlUtils.htmlEscape(clientAuthenticationUrlToClientName.getValue());                 sb.append(clientName);                 sb.append("</a>");                 sb.append("</td></tr>\n");             }             sb.append("</table>\n");         }         ...     } }
  • WAS ์‹คํ–‰ ํ›„, /login์— ์ ‘์†ํ•˜๋ฉด ์•„๋ž˜์™€ ๊ฐ™์€ ํŽ˜์ด์ง€๊ฐ€ ๋…ธ์ถœ ๋˜๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

<<code>/login</code> ํŽ˜์ด์ง€ ํ™”๋ฉด>

gt;

</login ํŽ˜์ด์ง€ ํ™”๋ฉด></

Untitled

/user


@RestController @RequestMapping("/user") public class UserController {      @GetMapping     public OAuth2User user(@AuthenticationPrincipal OAuth2User user) {         return user;     } }
  • @AuthenticationPrincipal : ํ˜„์žฌ ์ธ์ฆ๋œ ์‚ฌ์šฉ์ž์— ๋Œ€ํ•œ ์ •๋ณด์— ์‰ฝ๊ฒŒ ์ ‘๊ทผ ํ•ด์ฃผ๋Š” ์–ด๋…ธํ…Œ์ด์…˜
  • OAuth2User๋ฅผ ์ง์ ‘returnํ•˜์—ฌ ์ธ์ฆ๊ฐ์ฒด๋ฅผ JSON์œผ๋กœ ํ™•์ธ ๊ฐ€๋Šฅํ•˜๋‹ค.

๋กœ๊ทธ์ธ ๊ณผ์ • ์˜ˆ์‹œ - ๋„ค์ด๋ฒ„


  • Naver ํด๋ฆญ

<์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ์—์„œ ์ œ๊ณตํ•˜๋Š” ๋กœ๊ทธ์ธ ํ…œํ”Œ๋ฆฟ>

gt;

<์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ์—์„œ ์ œ๊ณตํ•˜๋Š” ๋กœ๊ทธ์ธ ํ…œํ”Œ๋ฆฟ></์Šคํ”„๋ง>

  • ๋„ค์ด๋ฒ„ ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€ ์ด๋™

<๋„ค์ด๋ฒ„ ๋กœ๊ทธ์ธ ํ™”๋ฉด>

gt;

<๋„ค์ด๋ฒ„ ๋กœ๊ทธ์ธ ํ™”๋ฉด></๋„ค์ด๋ฒ„>

  • ๋กœ๊ทธ์ธ ๊ณผ์ •์„ ๊ฑฐ์ณ ์„ฑ๊ณต ์‹œ์— /user๋กœ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ ๋˜๋Š” ๊ฒƒ์„ ํ™•์ธ ํ•  ์ˆ˜ ์žˆ๋‹ค.

<<code>/user</code>๋กœ ์ „๋‹ฌ๋œ ์ธ์ฆ ๊ฐ์ฒด ํ™•์ธ>

gt;

</user๋กœ ์ „๋‹ฌ๋œ ์ธ์ฆ ๊ฐ์ฒด ํ™•์ธ></

Spring OAuth2 Client ์‹ฌํ™” ์˜ˆ์ œ

๐Ÿ’ก ๊ฐ„๋‹จ ์˜ˆ์ œ์˜ ๊ฒฝ์šฐ, ๋‹จ์ˆœ SNS ๋กœ๊ทธ์ธ์„ ์ง„ํ–‰ ํ›„ `Spring Context`์— ์ธ์ฆ ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•ด ๋กœ๊ทธ์ธ์„ ํ•˜๋Š” ๊ณผ์ •๋งŒ์„ ๋‚˜ํƒ€๋‚ธ ๊ฒƒ์ด๋‹ค. ๋Œ€๋ถ€๋ถ„์˜ **ํผ๋ธ”๋ฆญ ์„œ๋น„์Šค**๋Š” `DB`์˜ ํšŒ์› ํ…Œ์ด๋ธ” ๊ฒ€์ฆ์„ ํ†ตํ•ด ์‹ค์ œ ํšŒ์›์ธ์ง€ ํ™•์ธ์„ ํ•˜๊ฑฐ๋‚˜ ์ถ”๊ฐ€์ ์œผ๋กœ ํšŒ์› ์ •๋ณด๋ฅผ ๋ฐ›์•„ ํšŒ์›๊ฐ€์ž…์„ ์ง„ํ–‰ํ•ด์•ผํ•˜๋Š” **์š”๊ตฌ ์‚ฌํ•ญ**์ด ์กด์žฌ ํ•œ๋‹ค. ์ด๋Ÿฌํ•œ ์š”๊ตฌ์‚ฌํ•ญ ๋•Œ๋ฌธ์— ๋กœ๊ทธ์ธ ํ•˜๋Š” ๊ณผ์ • ์ž‘์—… ์ค‘์— ์žˆ์–ด ์ปค์Šคํ…€์„ ํ•ด์•ผ๋˜๋Š” ์ด์Šˆ๋ฅผ ์‹ฌํ™” ์˜ˆ์ œ๋ฅผ ํ†ตํ•ด ์•Œ์•„ ๋ณด๊ฒ ๋‹ค.

UserDetailsService


  • UserDetailsService : ์ผ๋ฐ˜์ ์ธ Form(HTML)์„ ์ด์šฉํ•œ ๋กœ๊ทธ์ธ์€ ์œ ์ €์ •๋ณด๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ์ธํ„ฐํŽ˜์ด์Šค์ด๋‹ค.
  • UserDetails : UserDetailsService์˜ loadUserByUsername ๋ฆฌํ„ด ๊ฐ’์œผ๋กœ, ์œ ์ €์ •๋ณด๋ฅผ ๋‹ด๋Š” ๊ฐ์ฒด์ด๋‹ค.
public interface UserDetailsService {     UserDetails loadUserByUsername(String username) throws UsernameNotFoundException; }

[์ฐธ๊ณ ]

UserDetailsService : https://docs.spring.io/spring-security/reference/servlet/authentication/passwords/user-details-service.html
UserDetails : https://docs.spring.io/spring-security/reference/servlet/authentication/passwords/user-details.html

OAuth2 - UserDetailsService


  • OAuth2์—์„œ ์‚ฌ์šฉํ•˜๋Š” UserDetailsService๋Š” OAuth2UserService์ด๋‹ค.
  • OAuth2UserRequest : ์œ ์ €์ •๋ณด API๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ์š”์ฒญ ๊ฐ์ฒด๋ฅผ ์ธ์ž ๊ฐ’์œผ๋กœ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค.
  • OAuth2User๋ฅผ ์ƒ์† ๋ฐ›์€ ๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜
@FunctionalInterface public interface OAuth2UserService<R extends OAuth2UserRequest, U extends OAuth2User> {     U loadUser(R userRequest) throws OAuth2AuthenticationException; }
  • DefaultOAuth2UserService : OAuth2์—์„œ๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ OAuth2UserService ๊ตฌํ˜„ ํด๋ž˜์Šค๋ฅผ ์ œ๊ณตํ•ด์ฃผ๊ณ  ์žˆ๋‹ค.
  • loadUser(OAuth2UserRequest userRequest) : ์ฝ”๋“œ๋ฅผ ๋ณด๋ฉด CommonOAuth2Provider ๋˜๋Š” application.yml์—์„œ ์„ค์ •ํ•œ provider์˜ user-info-url์˜ API๋ฅผ ํ˜ธ์ถœํ•ด ์ „๋‹ฌ ๋ฐ›์€ Response๊ฐ’์„ DefaultOAuth2User๊ฐ์ฒด๋กœ ๋ณ€ํ™˜ํ•˜์—ฌ ๋ฐ˜ํ™˜ ํ•˜๊ณ  ์žˆ๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.
public class DefaultOAuth2UserService implements OAuth2UserService<OAuth2UserRequest, OAuth2User> {     ...     @Override     public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {         Assert.notNull(userRequest, "userRequest cannot be null");         if (!StringUtils.hasText(userRequest.getClientRegistration().getProviderDetails().getUserInfoEndpoint().getUri())) {             OAuth2Error oauth2Error = new OAuth2Error(MISSING_USER_INFO_URI_ERROR_CODE,                     "Missing required UserInfo Uri in UserInfoEndpoint for Client Registration: "                             + userRequest.getClientRegistration().getRegistrationId(),                     null);             throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());         }         String userNameAttributeName = userRequest.getClientRegistration().getProviderDetails().getUserInfoEndpoint()                 .getUserNameAttributeName();         if (!StringUtils.hasText(userNameAttributeName)) {             OAuth2Error oauth2Error = new OAuth2Error(MISSING_USER_NAME_ATTRIBUTE_ERROR_CODE,                     "Missing required \"user name\" attribute name in UserInfoEndpoint for Client Registration: "                             + userRequest.getClientRegistration().getRegistrationId(),                     null);             throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());         }         RequestEntity<?> request = this.requestEntityConverter.convert(userRequest);         ResponseEntity<Map<String, Object>> response = getResponse(userRequest, request);         Map<String, Object> userAttributes = response.getBody();         Set<GrantedAuthority> authorities = new LinkedHashSet<>();         authorities.add(new OAuth2UserAuthority(userAttributes));         OAuth2AccessToken token = userRequest.getAccessToken();         for (String authority : token.getScopes()) {             authorities.add(new SimpleGrantedAuthority("SCOPE_" + authority));         }         return new DefaultOAuth2User(authorities, userAttributes, userNameAttributeName);     }     ... }

DefaultOAuth2UserService - ์‚ฌ์šฉ์žํ™”


  • OAuth2UserService ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์ง์ ‘ ๊ตฌํ˜„ํ• ์ˆ˜๋„ ์žˆ์ง€๋งŒ, ์œ ์ €์ •๋ณด๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” API ๋กœ์ง๋„ ๊ตฌํ˜„์„ ํ•ด์•ผ๋˜๋Š” ์–ด๋ ค์›€์ด ์žˆ์–ด ๋‹ค์Œ ์˜ˆ์ œ์—์„œ๋Š” DefaultOAuth2UserService๋ฅผ ์ƒ์†๋ฐ›์•„์„œ ๊ตฌํ˜„ํ•ด๋ณด์ž.
  • ์ง€๊ธˆ ์˜ˆ์ œ์—์„œ๋Š” DB๋ฅผ ํ†ตํ•œ ํšŒ์›๊ฒ€์ฆ์„ ์ง„ํ–‰ํ•˜์ง€๋Š” ์•Š์ง€๋งŒ, ํ•„์š”ํ•˜๋‹ค๋ฉด ๋ณ„๋„๋กœ ๊ตฌํ˜„์ด ํ•„์š”ํ•˜๋‹ค.
@Service public class CustomOAuth2UserService extends DefaultOAuth2UserService {      @Override     public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {         OAuth2User oAuth2User = super.loadUser(userRequest);          // TODO : DB๋ฅผ ํ†ตํ•œ ํšŒ์›๊ฒ€์ฆ          return oAuth2User;     } }
  • CustomOAuth2UserService๋“ฑ๋ก
@Configuration @EnableWebSecurity @RequiredArgsConstructor public class SecurityConfig {      private final CustomOAuth2UserService userService;      @Bean     public SecurityFilterChain config(HttpSecurity http) throws Exception {         return http                 .authorizeRequests()                     .antMatchers("/login").permitAll()                     .anyRequest().authenticated()                     .and()                 .oauth2Login()                     .userInfoEndpoint()                         .userService(userService)                         .and()                     .defaultSuccessUrl("/user")                     .and()                 .build();     } }
  • WAS๋ฅผ ๋””๋ฒ„๊น… ๋ชจ๋“œ๋กœ ์‹คํ–‰์‹œ์ผœ ์ œ๋Œ€๋กœ ์ ์šฉ์ด ๋˜์—ˆ๋Š”์ง€ ํ™•์ธํ•ด๋ณด์ž.

Untitled

OpenID Connect(OIDC)

โš ๏ธ Google์˜ ๊ฒฝ์šฐ `break`๊ฐ€ ์•ˆ ๊ฑธ๋ฆฌ๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค. (์„ค์ •ํ•œ ๋‚˜๋จธ์ง€ ํ”Œ๋žซํผ๋“ค์€ ์ •์ƒ์ ์œผ๋กœ `break`๊ฐ€ ๊ฑธ๋ฆฐ๋‹ค.) ์ด์œ ๋Š” Google์€ `OpenID Connect` ๋ฐฉ์‹์œผ๋กœ ์ž‘๋™๋˜๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

  • ์•„๋ž˜๋Š”, ChatGPT์—๊ฒŒ โ€œOAuth2 vs OIDCโ€ ํ‚ค์›Œ๋“œ๋กœ ๋ฌผ์–ด๋ณธ ๋‹ต๋ณ€์˜ ์ผ๋ถ€๋ถ„์ด๋‹ค.
  • ์ฆ‰, OAuth2 ๊ธฐ๋ฐ˜์ด์ง€๋งŒ ๋‹ค๋ฅธ ์ธ์ฆ ๋ฐฉ์‹์ด๋‹ค.
  • ๊ถŒํ•œ ์š”์ฒญ์‹œ์—, scope ๊ฐ’์— openid๊ฐ€ ํฌํ•จ ๋˜์–ด ์žˆ๋‹ค๋ฉด OIDC ๋ฐฉ์‹์œผ๋กœ ์ธ์ฆ ์ง„ํ–‰

<โ€œOAuth2 vs OIDCโ€์˜ ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ - ChatGPT>

gt;

<โ€œoauth2 vs oidcโ€์˜ ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ - chatgpt></โ€œoauth2>

[์ฐธ๊ณ ] : https://webapp.chatgpt4google.com/s/MjYzMTU5

  • Google์˜ ์„ค์ • ์ •๋ณด๋ฅผ ๋‹ค์‹œ ์‚ดํŽด๋ณด๋ฉด, scope์— openid๊ฐ€ ํฌํ•จ๋˜์–ด์žˆ๋Š”๊ฑธ ํ™•์ธ ํ•  ์ˆ˜ ์žˆ๋‹ค.
GOOGLE {          @Override         public Builder getBuilder(String registrationId) {             ClientRegistration.Builder builder = getBuilder(registrationId,                     ClientAuthenticationMethod.CLIENT_SECRET_BASIC, DEFAULT_REDIRECT_URL);             builder.scope("openid", "profile", "email"); // openid ํฌํ•จ              builder.authorizationUri("https://accounts.google.com/o/oauth2/v2/auth");             builder.tokenUri("https://www.googleapis.com/oauth2/v4/token");             builder.jwkSetUri("https://www.googleapis.com/oauth2/v3/certs");             builder.issuerUri("https://accounts.google.com");             builder.userInfoUri("https://www.googleapis.com/oauth2/v3/userinfo");             builder.userNameAttributeName(IdTokenClaimNames.SUB);             builder.clientName("Google");             return builder;         }      }

OidcUserService - ์‚ฌ์šฉ์žํ™”


  • Spring OAuth2 Client์—์„œ OAuth2์˜ DefaultOAuth2UserService์™€ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ OIDC๋Š” OidcUserService๋ฅผ ๊ตฌํ˜„ ํด๋ž˜์Šค๋กœ ์ œ๊ณตํ•ด์ฃผ๊ณ  ์žˆ๋‹ค.
  • OAuth2UserService์„ ์ƒ์†๋ฐ›๊ณ  ํƒ€์ž… ๋งค๊ฐœ๋ณ€์ˆ˜๊ฐ€ OIDC๊ด€๋ จ ๊ฐ์ฒด์ธ ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.
  • OidUser ๋˜ํ•œ OAuth2User๋ฅผ ์ƒ์†๋ฐ›๊ณ  ์žˆ๋‹ค.
public class OidcUserService implements OAuth2UserService<OidcUserRequest, OidcUser> {     ...     @Override     public OidcUser loadUser(OidcUserRequest userRequest) throws OAuth2AuthenticationException {         Assert.notNull(userRequest, "userRequest cannot be null");         OidcUserInfo userInfo = null;         if (this.shouldRetrieveUserInfo(userRequest)) {             OAuth2User oauth2User = this.oauth2UserService.loadUser(userRequest);             Map<String, Object> claims = getClaims(userRequest, oauth2User);             userInfo = new OidcUserInfo(claims);             // https://openid.net/specs/openid-connect-core-1_0.html#UserInfoResponse             // 1) The sub (subject) Claim MUST always be returned in the UserInfo Response             if (userInfo.getSubject() == null) {                 OAuth2Error oauth2Error = new OAuth2Error(INVALID_USER_INFO_RESPONSE_ERROR_CODE);                 throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());             }             // 2) Due to the possibility of token substitution attacks (see Section             // 16.11),             // the UserInfo Response is not guaranteed to be about the End-User             // identified by the sub (subject) element of the ID Token.             // The sub Claim in the UserInfo Response MUST be verified to exactly match             // the sub Claim in the ID Token; if they do not match,             // the UserInfo Response values MUST NOT be used.             if (!userInfo.getSubject().equals(userRequest.getIdToken().getSubject())) {                 OAuth2Error oauth2Error = new OAuth2Error(INVALID_USER_INFO_RESPONSE_ERROR_CODE);                 throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());             }         }         Set<GrantedAuthority> authorities = new LinkedHashSet<>();         authorities.add(new OidcUserAuthority(userRequest.getIdToken(), userInfo));         OAuth2AccessToken token = userRequest.getAccessToken();         for (String authority : token.getScopes()) {             authorities.add(new SimpleGrantedAuthority("SCOPE_" + authority));         }         return getUser(userRequest, userInfo, authorities);     }     ... }
public interface OidcUser extends OAuth2User, IdTokenClaimAccessor {     ... }
@Service public class CustomOidcUserService extends OidcUserService {      @Override     public OidcUser loadUser(OidcUserRequest userRequest) throws OAuth2AuthenticationException {         OidcUser oidcUser = super.loadUser(userRequest);          // TODO : DB๋ฅผ ํ†ตํ•œ ํšŒ์›๊ฒ€์ฆ          return oidcUser;     } }
  • CustomOidcUserService ๋“ฑ๋ก
@Configuration @EnableWebSecurity @RequiredArgsConstructor public class SecurityConfig {      private final CustomOAuth2UserService userService;     private final CustomOidcUserService oidcUserService;      @Bean     public SecurityFilterChain config(HttpSecurity http) throws Exception {         return http                 .authorizeRequests()                     .antMatchers("/login").permitAll()                     .anyRequest().authenticated()                     .and()                 .oauth2Login()                     .userInfoEndpoint()                         .userService(userService)                         .oidcUserService(oidcUserService)                         .and()                     .defaultSuccessUrl("/user")                     .and()                 .build();     } }
  • ๋“ฑ๋ก ํ›„, breakpoint๋ฅผ ์„ค์ •ํ•˜๊ณ  WAS๋ฅผ ๋””๋ฒ„๊ทธ ๋ชจ๋“œ๋กœ ์‹คํ–‰์„ ํ•ด๋ณด๋ฉด Google์ผ ๋•Œ๋„ break ๊ฑธ๋ฆฌ๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

์ด๋ ‡๊ฒŒ Spring OAuth2 Client๋ฅผ ๊ฐ„๋‹จํžˆ(?) ์•Œ์•„๋ดค๋Š”๋ฐโ€ฆ
๋”์šฑ ์ƒ์„ธํ•˜๊ฒŒ ๋“ค์–ด๊ฐ€๋ฉด ์ด ๊ธ€์ด ๋๋‚˜์ง€ ์•Š์„ ๊ฒƒ ๊ฐ™์•„ ์—ฌ๊ธฐ์„œ ๋งˆ๋ฌด๋ฆฌ ํ•˜๊ฒ ๋‹ค. ๐Ÿ˜‚

(๋„ˆ๋ฌด ๋‚ด์šฉ์ด ๋”ฑ๋”ฑํ•˜๊ณ  ์ง„์ง€ํ•ด์„œ ์žฌ๋ฏธ๊ฐ€ ์—†์–ด ๊ฑฑ์ •์ด ๋œ๋‹คโ€ฆ.)

์กฐ๊ธˆ์ด๋‚˜๋งˆ, Spring OAuth2 Client์˜ ๊ฐœ๋…์— ๋Œ€ํ•œ ์ดํ•ด์™€ ์˜ˆ์ œ ๋“ค์„ ํ†ตํ•ด ๋•๊ธฐ ์œ„ํ•ด ์ž‘์„ฑํ•œ ๊ธ€์ด๋‹ค.
์ฐธ๊ณ ์™€ ์ถœ์ฒ˜๋ฅผ ํ†ตํ•ด ๋” ๋””ํ…Œ์ผํ•˜๊ฒŒ ์•Œ์•„ ๋ณด๋Š” ๊ฒƒ์„ ๊ถŒ์žฅํ•˜๊ณ , ํ•™์Šต ํ›„ ์„œ๋น„์Šค์— ์ ์šฉํ•˜๊ธฐ๋ฅผ ๋ฐ”๋ž€๋‹ค.

์œ„์˜ ์˜ˆ์ œ์—์„œ๋Š” 5๊ฐœ์˜ ๋กœ๊ทธ์ธ ํ”Œ๋žซํผ์„ ์—ฐ๋™ ์‹œ์ผœ๋ดค๋Š”๋ฐ,
์ด ์™ธ์—๋„ ๋Œ“๊ธ€๋กœ Twitter, Apple, Weibo ๋“ฑ ์š”์ฒญ์ด ๋‹ค์ˆ˜ ๋“ค์–ด์˜ค๋ฉด ๋‹ค์Œ ํฌ์ŠคํŠธ์—์„œ ์†Œ๊ฐœ ํ•˜๋„๋ก ํ•˜๊ฒ ๋‹ค.

Untitled

[์ถœ์ฒ˜]

https://velog.io/@tmdgh0221/Spring-Security-์™€-OAuth-2.0-์™€-JWT-์˜-์ฝœ๋ผ๋ณด

https://catsbi.oopy.io/f9b0d83c-4775-47da-9c81-2261851fe0d0

https://inpa.tistory.com/entry/WEB-๐Ÿ“š-OAuth-20-๊ฐœ๋…-๐Ÿ’ฏ-์ •๋ฆฌ

[Github]

https://github.com/discphy/oauth2-example

๋ฐ˜์‘ํ˜•