๐Ÿ“– Spring Security in Action

์ฑ…์˜ ๋ชจ๋“  ๋‚ด์šฉ์ด ์•„๋‹Œ ๊ธฐ์–ตํ•  ๋‚ด์šฉ + ์ถ”๊ฐ€ ์กฐ์‚ฌํ•œ ๋‚ด์šฉ์„ ์œ„์ฃผ๋กœ ์ •๋ฆฌํ•œ ํฌ์ŠคํŒ…์ž…๋‹ˆ๋‹ค.

Contents

  • Spring Security 5์—์„œ ์ถ”๊ฐ€๋œ ์ƒˆ๋กœ์šด ๊ธฐ๋Šฅ


  • Spring Security 5์—์„œ ์ถ”๊ฐ€๋œ ์ƒˆ๋กœ์šด ๊ธฐ๋Šฅ

    OAuth 2.0 ์ง€์› ๊ฐœ์„ 

    • OAuth 2.0 ํด๋ผ์ด์–ธํŠธ ์ง€์› ๊ฐœ์„ 
    • OAuth 2.0 ๋ฆฌ์†Œ์Šค ์„œ๋ฒ„ ์ง€์› ๊ฐœ์„ 
    • JWT(JWT Token) ์ง€์› ๊ฐœ์„ 
    • JDK 9 ๋ชจ๋“ˆ ์‹œ์Šคํ…œ ์ง€์›

    WebFlux ์ง€์›

    • Spring WebFlux๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ Spring Security๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

    PasswordEncoder ์ธํ„ฐํŽ˜์ด์Šค ๋ณ€๊ฒฝ

    • PasswordEncoder ์ธํ„ฐํŽ˜์ด์Šค๊ฐ€ ๋ณ€๊ฒฝ๋˜์–ด ๋”์šฑ ์œ ์—ฐํ•œ ๋น„๋ฐ€๋ฒˆํ˜ธ ์•”ํ˜ธํ™” ๊ตฌํ˜„์ด ๊ฐ€๋Šฅํ•ด์กŒ์Šต๋‹ˆ๋‹ค.

    Deprecated ํด๋ž˜์Šค ๋ฐ ๋ฉ”์„œ๋“œ ์ œ๊ฑฐ

    • AuthenticationProvider ์ธํ„ฐํŽ˜์ด์Šค์˜ Deprecated ๋ฐ ์ œ๊ฑฐ
    • RememberMeAuthenticationProvider ํด๋ž˜์Šค์˜ Deprecated ๋ฐ ์ œ๊ฑฐ ๋“ฑ



    1์žฅ. ์˜ค๋Š˜๋‚ ์˜ ๋ณด์•ˆ

    ์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ๋Š” ์•„ํŒŒ์น˜ 2.0 ๋ผ์ด์„ ์Šค์— ๋”ฐ๋ผ ๋ฆด๋ฆฌ์Šค๋˜๋Š” ์˜คํ”ˆ ์†Œ์Šค ์†Œํ”„ํŠธ์›จ์–ด์ž…๋‹ˆ๋‹ค. ์Šคํ”„๋ง ํ”„๋ ˆ์ž„์›Œํฌ์™€ ํ•จ๊ป˜ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋‹จ์œ„์˜ ๋ณด์•ˆ๊ฐœ๋ฐœ์„ ๋„์™€์ฃผ๋ฉฐ ์Šคํ”„๋ง์˜ ๋ฐฉ์‹์ธ ์–ด๋…ธํ…Œ์ด์…˜, ๋นˆ, SpEL(Spring Expression Language) ๋“ฑ์„ ์ด์šฉํ•ฉ๋‹ˆ๋‹ค.

    • ์•„ํŒŒ์น˜ ์‹œ๋กœ
    • GDRR(Genernal Data Protection Regulations, ์ผ๋ฐ˜ ๋ฐ์ดํ„ฐ ๋ณดํ˜ธ ๊ทœ์ •)

    ๋ณด์•ˆ์— ๋Œ€ํ•œ ๋ถ€๋ถ„์€ ์‹œ์Šคํ…œ์˜ ์•„ํ‚คํ…์ณ์ ์ธ ์„ฑ๊ฒฉ, ๋ชจ๋†€๋กœ์‹์ธ์ง€ ์•„๋‹ˆ๋ฉด ์—ฌ๋Ÿฌ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์œผ๋กœ ๊ตฌ์„ ๋œ ์‹œ์Šคํ…œ์ธ ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค ํŒจํ„ด์ธ์ง€์— ๋Œ€ํ•ด์„œ ๊ณ ๋ คํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์ธ์ฆ๊ณผ ๊ถŒํ•œ ๋ถ€์—ฌ ๋ถ€๋ถ„์—์„œ ํ•„์š” ์ด์ƒ์˜ ๊ถŒํ•œ์ด ๋ถ€์—ฌ๋  ์ˆ˜ ์—†๋„๋ก ์„ค์ •ํ•˜๋Š” ๊ฒƒ์ด ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค. ์ด์™ธ์—๋„ ๋ฐ์ดํ„ฐ ๋ณด์•ˆ์‹œ์— ์ „์†ก์ค‘์ธ ๋ฐ์ดํ„ฐ์™€ ์ €์žฅ๋œ ๋ฐ์ดํ„ฐ์˜ ๋ณด์•ˆ, ๋‚ด๋ถ€ ๋ฉ”๋ชจ๋ฆฌ ๊ด€๋ฆฌ, ํž™ ๋คํ”„์˜ ์ด์šฉ๊ด€๋ฆฌ ๋“ฑ ๋‹ค๋ฐฉ๋ฉด์œผ๋กœ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์ •๋ณด ๋ณด์•ˆ์„ ์œ„ํ•ด ๊ณ ๋ฏผํ•  ํ•„์š”๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.
    ์—ฌ๊ธฐ์„œ ์‚ดํŽด๋ณด๊ธฐ ์ข‹์€ ์˜คํ”ˆ์†Œ์Šค ์›น ๋ณด์•ˆ ํ”„๋กœ๊ทธ๋žจ์œผ๋กœ OWASP(Open Web Application Security Project)๊ฐ€ ์žˆ๋Š”๋ฐ, 10๋Œ€ ๋ณด์•ˆ ์ทจ์•ฝ์„ฑ์— ๋Œ€ํ•œ ๋ถ€๋ถ„๋“ค์„ ๋‹ค๋ฃจ๊ณ  ์žˆ๊ณ  ์„ธ๋ถ€ ํ•ญ๋ชฉ์œผ๋กœ๋Š” ์ธ์ฆ ์ทจ์•ฝ์„ฑ, ์„ธ์…˜ ๊ณ ์ •, XSS, CSRF, ์ฃผ์ž…, ๊ธฐ๋ฐ€ ๋ฐ์ดํ„ฐ ๋…ธ์ถœ, ๋ฉ”์„œ๋“œ ์ ‘๊ทผ ์ œ๊ฑฐ ๋ถ€์กฑ, ์•Œ๋ ค์ง„ ์ทจ์•ฝ์„ฑ์ด ์žˆ๋Š” ์ข…์†์„ฑ ์‚ฌ์šฉ ๋“ฑ์ด ์žˆ์Šต๋‹ˆ๋‹ค.

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

    ์„ธ์…˜ ๊ณ ์ •
    ๊ณต๊ฒฉ์ž๊ฐ€ ์ •ํ•œ ์„ธ์…˜ID๋ฅผ ์ด์šฉํ•ด์„œ ์‚ฌ์šฉ์ž๊ฐ€ ๋กœ๊ทธ์ธ ์ธ์ฆ์„ ํ•˜๊ฒŒ ๋˜๋Š” ๊ฒฝ์šฐ, ๊ณต๊ฒฉ์ž๋„ ๊ฐ™์€ ์„ธ์…˜ ID๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ์„ธ์…˜ ํ•˜์ด์žฌํ‚น์œผ๋กœ ์‚ฌ์šฉ์ž์™€ ๋™์ผํ•˜๊ฒŒ ์ •๋ณด๋ฅผ ๊ณต์œ ๋ฐ›๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

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

    CSRF
    ์‚ฌ์ดํŠธ ๊ฐ„ ์œ„์กฐ๋Š” ํŠน์ • ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ์ž‘๋™ํ•˜๋Š” url์ด ์™ธ๋ถ€์—์„œ ์žฌ์‚ฌ์šฉํ•˜๋ฏ€๋กœ์จ, url์˜ ์ผ๋ถ€ ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ๋ณ€๊ฒฝํ•˜๋Š” ๋“ฑ์˜ ๋ฐฉ์‹์œผ๋กœ ์‰ฝ๊ฒŒ ๋ณ€๊ฒฝํ•ด์„œ ํ”„๋กœ๊ทธ๋žจ์ด ์•…์šฉ๋  ์ˆ˜ ์žˆ๋„๋ก ํ•˜๋Š” ๋ฐฉ์‹์„ ๋งํ•ฉ๋‹ˆ๋‹ค.

    GET http: //banking.com/transfer.do?acct=John&amount=1000 HTTP/1.1
    GET http: //banking.com/transfer.do?acct=Mike&amount=5000
    

    ์ฃผ์ž…
    SQL ์ฃผ์ž…, XPath ์ฃผ์ž…, OS ๋ช…๋ น ์ฃผ์ž…, LDAP ์ฃผ์ž… ๋“ฑ์ด ์žˆ์Šต๋‹ˆ๋‹ค. ์ค‘์š”๋ฐ์ดํ„ฐ๋Š” ๋ณผํŠธ์— ๋„ฃ์–ด์ค˜์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๋ณผํŠธ๋Š” ์ผ๋ฐ˜์ ์œผ๋กœ ํด๋ผ์šฐ๋“œ ๊ธฐ๋ฐ˜์˜ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋ฐ ๋ฐ์ดํ„ฐ ์ €์žฅ์†Œ ์„œ๋น„์Šค์ธ๋ฐ, ์ค‘์š”ํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ์•ˆ์ „ํ•˜๊ฒŒ ์ €์žฅํ•˜๊ณ  ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ๋Š” ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

    LDAP(Lightweight Directory Access Protocol)์ด ๋ฌด์—‡์ผ๊นŒ?
    LDAP๋Š” ์‚ฌ์šฉ์ž๊ฐ€ ์กฐ์ง, ๊ตฌ์„ฑ์› ๋“ฑ์— ๋Œ€ํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ์ฐพ๋Š” ๋ฐ ๋„์›€์ด ๋˜๋Š” ํ”„๋กœํ† ์ฝœ์ž…๋‹ˆ๋‹ค. ๋Œ€๋ถ€๋ถ„ ๊ฒ€์ƒ‰์— ๋Œ€ํ•œ ์š”์ฒญ์œผ๋กœ DAP๋ณด๋‹ค ํ†ต์‹  ๋„คํŠธ์›Œํฌ ๋Œ€์—ญํญ ์ƒ์˜ ๊ฐ€๋ฒผ์šด ํŠน์„ฑ์ด ์žˆ์Šต๋‹ˆ๋‹ค. LDAP ์„œ๋ฒ„์—์„œ ์ฃผ๋กœ ํŠน์ • ๋ฐ์ดํ„ฐ๋ฅผ ์ค‘์•™์—์„œ ๊ด€๋ฆฌํ•˜๋Š” ๊ฒฝ์šฐ ํŠธ๋ฆฌ๊ตฌ์กฐ๋กœ ์ €์žฅํ•˜๊ฑฐ๋‚˜ ์กฐํšŒํ•˜๋Š” ๊ฒฝ์šฐ์— ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.

    ๊ธฐ๋ฐ€ ๋ฐ์ดํ„ฐ ๋…ธ์ถœ
    ๊ณต๊ฐœ ์ •๋ณด๋Š” ๋กœ๊ทธํ™”ํ•˜์ง€ ๋ง์•„์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด 500 ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ–ˆ์„ ๋•Œ, ๊ทธ๋Œ€๋กœ ์—๋Ÿฌ ๋‚ด์—ญ์ด ํด๋ผ์ด์–ธํŠธ ๋‹จ์— ๋…ธ์ถœ๋œ๋‹ค๋ฉด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๊ตฌ์กฐ์™€ ๊ทธ ์ข…์†์„ฑ๊นŒ์ง€ ๋‹ค ์•Œ ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ์œ„ํ—˜ํ•ฉ๋‹ˆ๋‹ค.



    2์žฅ. ์•ˆ๋…•! ์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ

    Spring-Security-Architecture-1024x576.jpg

    ์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ์—์„œ ์ง€์›๋˜๋Š” UserDetailService์™€ PasswordEncoder๋ฅผ ์žฌ์ •์˜ํ•ด์„œ ์œ„ ๊ทธ๋ฆผ์ฒ˜๋Ÿผ ์ธ์ฆ ๋…ผ๋ฆฌ๋ฅผ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค. ์ง€๊ธˆ์€ @Decrypted๋œ WebSecurityConfigurerAdapter๋กœ ์ฑ… ๋‚ด์šฉ์ด ์„ค๋ช…๋œ ๋ถ€๋ถ„๋“ค์ด ๋งŽ์•„ ์•ฝ๊ฐ„์˜ ์ฐจ์ด์ ์€ ์žˆ์ง€๋งŒ ์ธ์ฆ ๋…ผ๋ฆฌ๋ฅผ ์œ„ํ•ด config๋ฅผ ๊ตฌ์„ฑํ•˜๋Š” ํฐ ํ๋ฆ„์€ ๋ณ€ํ™”๊ฐ€ ์—†๋‹ค๋Š” ๋ถ€๋ถ„์— ์ค‘์‹ฌ์„ ๋‘๊ณ  ๋ณด๋Š” ๊ฒƒ์ด ์ข‹์„ ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค.

    • UserDetailService ๊ณ„์•ฝ์„ ๊ตฌํ˜„ํ•˜๋Š” ๊ฐ์ฒด๋กœ ์‚ฌ์šฉ์ž์— ๋Œ€ํ•œ ์„ธ๋ถ€์ •๋ณด๋ฅผ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค. ๋‚ด๋ถ€ ๋ฉ”๋ชจ๋ฆฌ์— ๊ธฐ๋ณธ ์ž๊ฒฉ ์ฆ๋ช…์„ ๋“ฑ๋กํ•˜๋ฏ€๋กœ ์Šคํ”„๋ง ์ปจํ…์ŠคํŠธ๊ฐ€ ๋กœ๋“œ๋  ๋•Œ ์ž๋™์œผ๋กœ ์ƒ์„ฑ๋ฉ๋‹ˆ๋‹ค.
    • PasswordEncoder๋Š” ๋ง ๊ทธ๋Œ€๋กœ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์•”ํ˜ธํ™” ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ์•”ํ˜ธํ™” ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ์•”ํ˜ธํ™”๋œ ๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ๊ธฐ์กด ์ธ์ฝ”๋”ฉ๋œ ๋น„๋ฐ€๋ฒˆํ˜ธ์™€์˜ ์ผ์น˜๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋„๋ก match ๋ฉ”์„œ๋“œ๋ฅผ ์ œ๊ณตํ•˜๋Š” ๊ธฐ๋Šฅ์„ ํ•ฉ๋‹ˆ๋‹ค.

    ์ง€๊ธˆ ์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ์™€ ๋‹ฌ๋ผ์ง„ ๋ถ€๋ถ„์ด ๋งŽ์•„์„œ ์ฑ…์—์„œ ์ œ์‹œํ•˜๋Š” ๊ตฌํ˜„๋‹จ ์˜ˆ์‹œ๋งŒ ๋ช‡ ๊ฐ€์ง€ ์ž‘์„ฑํ–ˆ์Šต๋‹ˆ๋‹ค.

    ๊ตฌํ˜„ ์‚ฌ๋ก€๋กœ ๋“  ์˜ˆ์‹œ ์ฝ”๋“œ 1
    UserDetailService์™€ PasswordEncoder๋ฅผ ๊ตฌํ˜„ํ•˜๋Š” ๋‹ค์–‘ํ•œ ๋ฐฉ๋ฒ•์ด ์žˆ์ง€๋งŒ, ๋‘ ๋ถ€๋ถ„์˜ ๋ฉ”์„œ๋“œ๋ฅผ @Bean์œผ๋กœ ์„ค์ •ํ•˜์ง€ ์•Š๊ณ  ํ•œ ๋ฒˆ์— ๊ฐ™์€ config๋กœ ์„ค์ •ํ•˜๋Š” ๊ฒฝ์šฐ์—๋Š” ๊ตฌ์„ฑ์ด ํ˜ผํ•ฉ๋˜์–ด ์ฑ…์ž„์ด ๋ถ„๋ฆฌ๋˜์ง€ ์•Š์•˜๊ธฐ ๋•Œ๋ฌธ์— ๊ถŒ์žฅํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
    ์•„๋ž˜ ์ฝ”๋“œ์—์„œ๋Š” @Deprecated๋œ NoOpPasswordEncoder๋ฅผ ์‚ฌ์šฉํ•ด์„œ ์•”ํ˜ธํ™”๋ฅผ ๊ตฌํ˜„ํ–ˆ์ง€๋งŒ, ์‹ค์ œ๋กœ๋Š” return PasswordEncoderFactories.createDelegatingPasswordEncoder();์™€ ๊ฐ™์€ ๋ฐฉ์‹ ๋˜๋Š” ๊ฐœ๋ฐœ์ž ๋‚˜๋ฆ„์˜ ์žฌ์ •์˜๋ฅผ ํ†ตํ•ด ์ž‘์„ฑํ•˜๋Š” ๊ฒƒ์ด ๋ฐ”๋žŒ์งํ•ฉ๋‹ˆ๋‹ค.

    @Configuration
    public class UserManagementConfig {
        @Bean
        public UserDetailsService userDetailsService() {
            var userDetailsService = new InMemoryUserDetailsManager();
            var user = User.withUsername("john")
                    .password("12345")
                    .authorities("read")
                    .build();
            userDetailsService.createUser(user);
            return userDetailsService;
        }
    
        @Bean
        public PasswordEncoder passwordEncoder() {
            return NoOpPasswordEncoder.getInstance();
        }
    }
    

    ๊ตฌํ˜„ ์‚ฌ๋ก€๋กœ ๋“  ์˜ˆ์‹œ ์ฝ”๋“œ 2
    UserDetailService์™€ PasswordEncoder๋ฅผ ๊ตฌ ์ด ๋‘ ๊ตฌ์„ฑ์š”์†Œ์— ์ž‘์—…์„ ์œ„์ž„ํ•˜๋Š” AuthenticationProvider(์ธ์ฆ๊ณต๊ธ‰์ž)๋ฅผ ๊ตฌํ˜„ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  AuthenticationManagerBuilder๋ฅผ ์ด์šฉํ•œ ๊ตฌํ˜„์„ config์—์„œ AuthenticationProvider(์ธ์ฆ๊ณต๊ธ‰์ž)์„ ์ฃผ์ž…๋ฐ›์•„์„œ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ฒฝ์šฐ์˜ ์‚ฌ๋ก€๋„ ์žˆ์Šต๋‹ˆ๋‹ค.

    @Component
    public class CustomAuthenticationProvider implements AuthenticationProvider {
        @Override
        public Authentication authenticate(Authentication authentication) throws AuthenticationException {
            String username = authentication.getName();
            String password = String.valueOf(authentication.getCredentials());
            if ("john".equals(username) && "12345".equals(password)) {
                return new UsernamePasswordAuthenticationToken(username, password, Arrays.asList());
            } else {
                throw new AuthenticationCredentialsNotFoundException("Error!");
            }
        }
    
        @Override
        public boolean supports(Class<?> authenticationType) {
            return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authenticationType);
        }
    }
    



    3์žฅ. ์‚ฌ์šฉ์ž ๊ด€๋ฆฌ

    3์žฅ์€ User, UserDetailService, UserDetailManager์— ๋Œ€ํ•œ ๋‚ด์šฉ์„ ๋‹ค๋ฃจ๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž๊ฐ€ ์ธ์ฆ์„ ํ•˜๋Š” ๊ณผ์ •์—์„œ ์ธ์ฆ ๋…ผ๋ฆฌ์— ๋”ฐ๋ผ ์ธ์ฆ ์ œ๊ณต์ž๋Š” ์‚ฌ์šฉ์ž๋ฅผ ์ธ์ฆํ•˜๋Š” ๊ณผ์ •์„ ๊ฑฐ์น˜๊ฒŒ ๋˜๋Š”๋ฐ ์ด ๋•Œ, ๋ฉ”๋ชจ๋ฆฌ User๋ฅผ ๊ด€๋ฆฌํ•˜๋Š” ์ฒด๊ณ„์— ๋Œ€ํ•œ ๋‚ด์šฉ์ž…๋‹ˆ๋‹ค.


    UserDetils์™€ ๊ตฌํ˜„
    ์‚ฌ์šฉ์ž ๊ธฐ์ˆ ์„ ์œ„ํ•ด์„œ ์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ๋Š” UserDetils ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ตฌํ˜„ํ•˜๊ณ  ์ค€์ˆ˜ํ•ฉ๋‹ˆ๋‹ค. UserDetails๋ฅผ ์ง์ ‘ ํด๋ž˜์Šค๋กœ ๊ตฌํ˜„ํ•ด๋„ ๋˜๊ณ , ์‚ฌ์šฉ์— ๋”ฐ๋ผ UserDetail๋ฅผ ๋นŒ๋“œํ•ด์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. UserDetails๋Š” ํ•˜๋‚˜ ์ด์ƒ์˜ ๊ถŒํ•œ, password, username์„ ์กฐํšŒํ•˜๊ฑฐ๋‚˜ ๊ณ„์ •์˜ ํ™œ์„ฑํ™” ๋ฐ ๋น„ํ™œ์„ฑํ™”๋ฅผ ๊ด€๋ฆฌํ•˜๋Š” ๋ฉ”์„œ๋“œ๋“ค์„ ๊ฐ€์ง€๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ๊ณ„์ •์˜ ๊ด€๋ฆฌ๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ true ์ฒ˜๋ฆฌ๋ฅผ ํ•˜์ง€๋งŒ ๋ณ„๋„ ๋‚ด๋ถ€ ์„œ๋น„์Šค ๋ฐฉ์นจ์— ๋”ฐ๋ผ ์ปค์ฆˆํ„ฐ๋งˆ์ด์ง•ํ•ด ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

    public interface UserDetails extends Serializable {
    	Collection<? extends GrantedAuthority> getAuthorities();
    	String getPassword();
    	String getUsername();
    	boolean isAccountNonExpired();
    	boolean isAccountNonLocked();
    	boolean isCredentialsNonExpired();
    	boolean isEnabled();
    }
    

    ์ด๋ ‡๊ฒŒ UserDetails๋ฅผ ํ•˜๋‚˜์˜ ํด๋ž˜์Šค๋กœ implements ํ•ด์„œ ๊ตฌํ˜„ํ•˜๋Š” ๋ฐฉ๋ฒ•๋„ ์žˆ์ง€๋งŒ, UserDetailService๋ฅผ ์œ„ํ•ด ์ œ๊ณต๋˜๋Š” User ๋ชจ๋ธ์„ ๋นŒ๋“œํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ๋ชจ๋ธ๋„ UserDetails ๊ตฌํ˜„์ฒด์ž…๋‹ˆ๋‹ค. ์ด ๊ฒฝ์šฐ withUsername ๋ฉ”์„œ๋“œ๋ฅผ ์ด์šฉํ•ด UserBuilder๋ฅผ ๋งŒ๋“ค์–ด UserDetiails ๊ฐ์ฒด๋ฅผ ๋งŒ๋“ค ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.

    UserDetails userDetails = User.withUsername(member.getEmail())
                                .password(member.getPassword())
                                .roles(member.getRole())
                                .build();
    


    UserDetailService์™€ ์„ธ ๊ฐ€์ง€ UserDetailManager์— ๋Œ€ํ•˜์—ฌ
    UserDetailService๋Š” ์ธ์ฆ์˜ ํ•ต์‹ฌ์ด ๋˜๋Š” ๋ถ€๋ถ„์œผ๋กœ ์ธ์ฆ ๋…ผ๋ฆฌ์—์„œ ๋ฉ”๋ชจ๋ฆฌ ๋‚ด์—์„œ User๋ฅผ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค. UserDetailService ์ธํ„ฐํŽ˜์ด์Šค๋Š” ๋‹จ ํ•œ ๊ฐ€์ง€ ๋ฉ”์„œ๋“œ๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๋Š”๋ฐ loadUserByUsername(String username)๋ฅผ ๊ฐ€์ง€๊ณ  Username์— ํ•ด๋‹นํ•˜๋Š” ์‚ฌ์šฉ์ž๊ฐ€ ์žˆ๋Š”์ง€ ๋ฉ”๋ชจ๋ฆฌ์—์„œ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค. ํ•ด๋‹นํ•˜๋Š” ์‚ฌ์šฉ์ž๊ฐ€ ์—†๋‹ค๋ฉด, UsernameNotFoundException๋กœ RuntimeException๋ฅผ ๋˜์ง‘๋‹ˆ๋‹ค.

    public interface UserDetailsService {
    	UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
    }
    

    ์ฑ…์—์„œ ์ œ์‹œํ•œ loadUserByUsername ๊ตฌํ˜„์˜ ์˜ˆ์ž…๋‹ˆ๋‹ค. User๋“ค ์ค‘์—์„œ username์ด ๋™์ผํ•œ User๋งŒ์„ ์ถ”์ถœํ•ด UserDetails๋กœ ๋ฆฌํ„ดํ•˜๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException{
      return Users.stream()
        .filter(u -> u.getUsername().equals(username))
        .findFirst()
        .orElseThrows(() -> new UsernameNotFoundException("User not found"));
    }
    

    UserDetailManager๋Š” UserDetailService์˜ ๊ธฐ๋Šฅ์„ ํ™•์žฅํ•˜๊ณ  ๋ฉ”์„œ๋“œ๋ฅผ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค. UserDetailManager์˜ ๊ตฌํ˜„ํด๋ž˜์Šค๋กœ InMemoryUserDetailsManager์™€ JdbcUserDetailsManager๋ฅผ ์ œ๊ณตํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ๋‚ด๋ถ€ ๋ฉ”๋ชจ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜๋Š”์ง€ ๋˜๋Š” SQL ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ €์žฅ๋œ ์‚ฌ์šฉ์ž๋ฅผ ๊ด€๋ฆฌํ•˜๋ฉฐ JDBC๋ฅผ ํ†ตํ•ด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ง์ ‘ ์—ฐ๊ฒฐํ•˜๋Š”์ง€์— ๋”ฐ๋ผ ๋‹ค๋ฅด๊ฒŒ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.
    LdapUserDetailsManager๋„ ์ œ๊ณตํ•˜๊ณ  ์žˆ์ง€๋งŒ LDAP๋ฅผ ์‚ฌ์šฉํ•  ๊ฒฝ์šฐ ๋ณ„๋„ dependency ์„ค์ •์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

    public interface UserDetailsManager extends UserDetailsService {
    	void createUser(UserDetails user);
    	void updateUser(UserDetails user);
    	void deleteUser(String username);
    	void changePassword(String oldPassword, String newPassword);
    	boolean userExists(String username);
    }
    

    ์ถ”๊ฐ€๋กœ setUsersByUsernameQuery ๋“ฑ๊ณผ ๊ฐ™์ด JdbcUserDetailsManager์˜ ์ฟผ๋ฆฌ๋ฅผ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ @Bean์œผ๋กœ UserDetailsService config๋กœ ๋“ฑ๋กํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

    @Bean
    public UserDetailsService userDetailsService(DataSource dataSource) {
        String usersByUsernameQuery = "select username, password, enabled from spring.users where username = ?";
        String authsByUserQuery = "select username, authority from spring.authorities where username = ?";
        var userDetailsManager = new JdbcUserDetailsManager(dataSource);
        userDetailsManager.setUsersByUsernameQuery(usersByUsernameQuery);
        userDetailsManager.setAuthoritiesByUsernameQuery(authsByUserQuery);
        return userDetailsManager;
    
    }
    



    4์žฅ. ์•”ํ˜ธ์ฒ˜๋ฆฌ

    ์ธ์ฆ๊ณต๊ธ‰์ž๊ฐ€ ์ œ๊ณตํ•œ password๋ฅผ ์ด์šฉํ•ด์„œ PasswordEncorder๋ฅผ ์ด์šฉํ•ด์„œ ์•”ํ˜ธ๋ฅผ ๊ฒ€์ฆํ•ฉ๋‹ˆ๋‹ค. ์ด ๋ถ€๋ถ„์€ ์ž‘์„ฑ์‹œ์ ์ธ ํ˜„์žฌ(2023-09)์™€ ์ฐจ์ด๊ฐ€ ์ข€ ์žˆ์ง€๋งŒ ํ”„๋กœ์„ธ์Šค๋ฅผ ์ดํ•ดํ•˜๊ธฐ ์œ„ํ•œ ๊ฒƒ์œผ๋กœ ์ฑ…์„ ๊ธฐ๋ฐ˜์œผ๋กœ ์„œ์ˆ ํ•˜๊ณ ์ž ํ•ฉ๋‹ˆ๋‹ค.

    PasswordEncorder ์ธํ„ฐํŽ˜์ด์Šค
    encode()๋ฅผ ํ†ตํ•ด ์•”ํ˜ธํ™”๋ฅผ matches()๋ฅผ ํ†ตํ•ด ์ธ์ฝ”๋”ฉ๋œ ๋ฌธ์ž์—ด์ด ์•”ํ˜ธ์™€ ์ผ์น˜์—ฌ๋ถ€๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. upgradeEncoding(CharSequence encodedPassword)๋Š” ๊ธฐ๋ณธ๊ฐ’์ด false์ด์ง€๋งŒ true ์ฒ˜๋ฆฌํ•˜๋Š” ๊ฒฝ์šฐ ์ธ์ฝ”๋”ฉ๋œ ์•”ํ˜ธ๋ฅผ ๋‹ค์‹œ ์ธ์ฝ”๋”ฉํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

    Spring security์—์„œ ์ œ๊ณตํ•˜๋Š” PasswordEncoder ๊ตฌํ˜„ ์˜ต์…˜๋“ค์€ ๋‹ค์Œ ์˜ต์…˜๋“ค์ด ์žˆ์Šต๋‹ˆ๋‹ค. ๊ฐ ํ•ด์‹ฑ ์•Œ๊ณ ๋ฆฌ์ฆ˜์— ๋Œ€ํ•œ ์„ค๋ช…

    • NoOpPasswordEncoder
    • BCryptPasswordEncoder
    • Pbkdf2PasswordEncoder
    • SCryptPasswordEncoder
    • Argon2PasswordEncoder
    • DelegatingPasswordEncoder

    NoOpPasswordEncoder
    ํ…Œ์ŠคํŠธ๋‚˜ ์ด์ „ ์‹œ์Šคํ…œ๊ณผ์˜ ํ˜ธํ™˜์„ฑ์„ ์œ„ํ•œ ๊ฒฝ์šฐ์—๋งŒ ์‚ฌ์šฉ๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ํ˜„์žฌ๋Š” Deprecated ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

    BCryptPasswordEncoder

    @Bean  
    public BCryptPasswordEncoder passwordEncoder() {  
    	return new BCryptPasswordEncoder(16);  
    }
    

    ์ด๋ ‡๊ฒŒ ์ธ์ฝ”๋”ฉ ํ”„๋กœ์„ธ์Šค์— ์ด์šฉ๋˜๋Š” ๋กœ๊ทธ ๋ผ์šด๋“œ๋ฅผ ๋‚˜ํƒ€๋‚ด๋Š” ๊ฐ•๋„ ๊ณ„์ˆ˜๋ฅผ ์ง€์ •ํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ์•„๋ž˜ ์˜ˆ์ œ์—์„œ 4๋Š” ๊ฐ•๋„ ๊ณ„์ˆ˜์ž…๋‹ˆ๋‹ค. ์ด ๊ฐ’์€ 2์˜ 4์ œ๊ณฑ ์ฆ‰, 16๋ฒˆ์˜ ํ•ด์‹ฑ ๋ผ์šด๋“œ๋ฅผ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค. b๋Š” BCrypt์—์„œ ์†”ํŠธ(salt)๋ฅผ ์ƒ์„ฑํ•  ๋•Œ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.

    SecureRandom s = SecureRandom.getInstanceStrong();
    PasswordEncoder p = new BCryptPasswordEncoder(4, s);
    

    DelegatingPasswordEncoder
    ์—ฌ๋Ÿฌ ํ•ด์‹ฑ ์ „๋žต์„ ์œ ์—ฐํ•˜๊ฒŒ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋„์™€์ค๋‹ˆ๋‹ค. ์ ‘๋‘์‚ฌ {noop}์— ๋Œ€ํ•ด NoOpasswordEncoder๊ฐ€, {bcrypt}์ธ ๊ฒฝ์šฐ์—๋Š” BCryptPasswordEncoder, {scrypt}์ด๋ฉด, SCryptPasswordEncoder๋ฅผ ๋“ฑ๋กํ•ฉ๋‹ˆ๋‹ค. ์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ์—๋Š” DelegatingPasswordEncoder์˜ ๊ตฌํ˜„์„ ๋ฐ˜ํ™˜ํ•˜๋Š” ์ •์  ๋ฉ”์„œ๋“œ๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

    PasswordEncoder passwordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
    

    ์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ์—์„œ DelegatingPasswordEncoder๋ฅผ ๊ธฐ๋ณธ์ ์œผ๋กœ ์‚ฌ์šฉํ•˜๊ฒŒ ๋˜๋ฉด์„œ, NoOpPasswordEncoder์™€ ๊ฐ™์€ deprecated๋œ PasswordEncoder๋ฅผ ํฌํ•จํ•˜์—ฌ ์—ฌ๋Ÿฌ ์ „๋žต์„ ์ง€์›ํ•˜๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ์ด๋•Œ, NoOpPasswordEncoder์— ๋Œ€ํ•œ deprecated ๊ฒฝ๊ณ ๋ฅผ ํ”ผํ•˜๊ธฐ ์œ„ํ•ด createDelegatingPasswordEncoder ๋ฉ”์„œ๋“œ์— @SuppressWarnings("deprecation")์„ ์ถ”๊ฐ€๋œ ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.


    ๊ทธ๋Ÿผ ๋น„๋ฐ€๋ฒˆํ˜ธ ์•”ํ˜ธํ™” ์™ธ์—๋Š” ์–ด๋–ป๊ฒŒ ์•”ํ˜ธํ™”๋ฅผ ๊ตฌํ˜„ํ• ๊นŒ์š”?
    ์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ ์•”ํ˜ธํ™” ๋ชจ๋“ˆ(SSCM)์—๋Š” ํ‚ค ์ƒ์„ฑ๊ธฐ์™€ ์•”ํ˜ธ๊ธฐ๋ฅผ ๊ตฌํ˜„ํ•˜๋Š” ๋Œ€์•ˆ์ด ์žˆ์Šต๋‹ˆ๋‹ค.

    StringKeyGenerator keyGenerator = KeyGenerators.string();
    String salt = keyGenertor.generateKey();
    
    
    BytesKeyGenertor keyGenerator = KeyGenerators.secureRandom(16);
    
    BytesKeyGenertor keyGenerator = KeyGenerators.shared(16);
    

    ์•”ํ˜ธ๊ธฐ๋Š” ์•”ํ˜ธํ™” ์•Œ๊ณ ๋ฆฌ์ฆ˜์„ ๊ตฌํ˜„ํ•˜๋Š” ๊ฐ์ฒด์ž…๋‹ˆ๋‹ค. BytesEncryptor์™€ TextEncryptor๋ผ๋Š” ์•”ํ˜ธ๊ธฐ๊ฐ€ ์žˆ๊ณ  ๊ฐ๊ฐ์€ ๋‹ค๋ฅธ ๋ฐ์ดํ„ฐ ํ˜•์‹์„ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค. BytesEncryptor๊ฐ€ ๋ฌธ์ž์—ด๋กœ ์ถœ๋ ฅ์„ ๋ฐ˜ํ™˜ํ•˜๋Š” ๋ฐ˜๋ฉด, TextEncryptor๋Š” ๋” ๋ฒ”์šฉ์ ์ด๊ณ  ๋ฐ”์ดํŠธ ๋ฐฐ์—ด๋กœ ์ž…๋ ฅ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์Šต๋‹ˆ๋‹ค.

    BytesEncryptor e = Encryptors.stronger(password, salt);
    

    TextEncryptor๋Š” Encryptors.text(), Encryptors.delux(), Encryptors.queryableText()์˜ ์„ธ ๊ฐ€์ง€ ์ฃผ์š” ํ˜•์‹์„ ๊ฐ€์ง€๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. Encryptors.text(), Encryptors.delux()๋Š” encrypt() ๋ฉ”์„œ๋“œ๋ฅผ ๋ฐ˜๋ณต ํ˜ธ์ถœํ•ด๋„ ๋‹ค๋ฅธ ์ถœ๋ ฅ์ด ๋ฐ˜ํ™˜๋˜๋Š”๋ฐ, ์ดˆ๊ธฐํ™” ๋ฒกํ„ฐ๊ฐ€ ์ƒ์„ฑ๋˜๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. Encryptors.queryableText()๋Š” ์ฟผ๋ฆฌ ๊ฐ€๋Šฅ ํ…์ŠคํŠธ๋กœ ์ž…๋ ฅ์ด ๊ฐ™์œผ๋ฉด ๊ฐ™์€ ์ถœ๋ ฅ์„ ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฒƒ์„ ๋ณด์žฅํ•ฉ๋‹ˆ๋‹ค.

    TextEncryptor e = Encryptors.queryableText(password, salt);
    String encrypted = e.encrypt(valueToEncrypt);
    



    5์žฅ. ์ธ์ฆ ๊ตฌํ˜„

    • ๋งž์ถคํ˜• AuthenticationProvider๋กœ ์ธ์ฆ ๋…ผ๋ฆฌ ๊ตฌํ˜„
    • HTTP Basic ๋ฐ ์–‘์‹ ๊ธฐ๋ฐ˜ ๋กœ๊ทธ์ธ ์ธ์ฆ ๋ฉ”์„œ๋“œ ์ด์šฉ
    • SecurityContext ๊ตฌ์„ฑ ์š”์†Œ์˜ ์ดํ•ด ๋ฐ ๊ด€๋ฆฌ

    ์ธ์ฆ ํ•„ํ„ฐ๊ฐ€ ๊ฐ€๋กœ์ฑˆ ์š”์ฒญ์„ ๊ทธ๋ฆฌ๊ณ  ๊ทธ ์ธ์ฆ ์ฑ…์ž„์„ ์ธ์ฆ ๊ด€๋ฆฌ์ž์— ์œ„์ž„ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์ธ์ฆ ๊ณต๊ธ‰์ž์—์„œ ํ™•์ธํ•œ ์ธ์ฆ ๊ฒฐ๊ณผ๋Š” ๋ณด์•ˆ ์ปจํ…์ŠคํŠธ(SecurityContext)์— ์ €์žฅ๋ฉ๋‹ˆ๋‹ค.

    AuthenticationProvider์˜ ์ดํ•ด
    Authentication ๊ณ„์•ฝ์ธ Principal ๊ณ„์•ฝ์„ ์ƒ์†ํ•˜๋Š”๋ฐ Authentication์—์„œ๋Š” ์•”ํ˜ธ๊ฐ™์€ ์š”๊ตฌ ์‚ฌํ•ญ์ด๋‚˜ ์ธ์ฆ ์š”์ฒญ์— ๋Œ€ํ•œ ์„ธ๋ถ€ ์ •๋ณด๋ฅผ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Š” ์ž๋ฐ” ์‹œํ๋ฆฌํ‹ฐ API์˜ Principal ๊ณ„์•ฝ์„ ํ™•์žฅํ•˜๋„๋ก ์„ค๊ณ„๋˜์–ด ํ˜ธํ™˜์„ฑ ์ธก๋ฉด์— ์ด์ ์ด ๋ฉ๋‹ˆ๋‹ค. (๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์ด์ )

    public interface Authentication extends Principal, Serializable {  
    	Collection<? extends GrantedAuthority> getAuthorities();  
    	Object getCredentials();  
    	Object getDetails();  
    	Object getPrincipal();   
    	boolean isAuthenticated();    
    	void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;  
    }
    

    AuthenticationProvider ์ธํ„ฐํŽ˜์ด์Šค๋Š” ์‚ฌ์šฉ์ž๋ฅผ ์ฐพ๋Š” ๊ฒƒ์€ UserDetailsService์— ์œ„์ž„ํ•˜๊ณ  PasswordEncoder๋กœ ์ธ์ฆ ํ”„๋กœ์„ธ์Šค์—์„œ ์•”ํ˜ธ๋ฅผ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

    ํ•˜์ง€๋งŒ, AuthenticationProvider๋Š” Spring Security 5๋ถ€ํ„ฐ Deprecated ๋˜์—ˆ์œผ๋ฉฐ, ๋Œ€์‹ ์— AuthenticationManager๋ฅผ ์‚ฌ์šฉํ•˜๋„๋ก ๊ถŒ์žฅ๋ฉ๋‹ˆ๋‹ค. AuthenticationManager๋Š” AuthenticationProvider์™€ ๋‹ค์–‘ํ•œ ์ธ์ฆ ๋ฐฉ์‹์„ ์ง€์›ํ•˜๋Š” Provider๋“ค์„ ํ†ตํ•ฉ์ ์œผ๋กœ ๊ด€๋ฆฌํ•˜๊ณ , ์‹ค์ œ ์ธ์ฆ ์ฒ˜๋ฆฌ๋ฅผ ๋‹ด๋‹นํ•˜๋Š” ์—ญํ• ์„ ์ˆ˜ํ–‰ํ•˜๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.

    ๊ตฌํ˜„๋ถ€์˜ AuthenticationProvider์™€ WebSecurityConfigurerAdapter ๋‘˜ ๋‹ค ํ˜„์žฌ๋Š” ์‚ฌ์šฉ๋˜์ง€ ์•Š๋Š” ๋ถ€๋ถ„์œผ๋กœ ์ž‘์„ฑํ•˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.


    ๊ธ€ ๋งˆ์ง€๋ง‰์— ์ด๋Ÿฐ ๋‚ด์šฉ์ด ์กด์žฌํ•˜๋Š”๋ฐ ์ด ๋ถ€๋ถ„์ด ์˜คํžˆ๋ ค ์ฑ…์˜ ํ•ต์‹ฌ ๋‚ด์šฉ์ด์ž, ์˜์กด์„ฑ์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฐœ๋ฐœ์ž๋“ค์ด ํ•„ํžˆ ๊ณ ๋ คํ•ด์•ผ ํ•  ๋ถ€๋ถ„์ด๋ผ๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค.

    ํ”„๋ ˆ์ž„์›Œํฌ, ํŠนํžˆ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— ๋„๋ฆฌ ์ด์šฉ๋˜๋Š” ํ”„๋ ˆ์ž„์›Œํฌ๋Š” ์ˆ˜๋งŽ์€ ๋˜‘๋˜‘ํ•œ ๊ฐœ๋ฐœ์ž์˜ ์ฐธ์—ฌ๋กœ ๊ฐœ๋ฐœ๋œ๋‹ค. ๊ทธ๋ ‡๋‹ค๊ณ  ํ•ด๋„ ํ”„๋ ˆ์ž„์›Œํฌ๊ฐ€ ์ž˜๋ชป ๊ตฌํ˜„๋˜๋Š” ๊ฒฝ์šฐ๋Š” ๋งŽ์ง€ ์•Š๋‹ค. ์–ด๋–ค ๋ฌธ์ œ๊ฐ€ ํ”„๋ ˆ์ž„์›Œํฌ์˜ ์ž˜๋ชป์ด๋ผ๊ณ  ๊ฒฐ๋ก  ๋‚ด๋ฆฌ๊ธฐ ์ „์— ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋ถ„์„ํ•˜์ž.

    ํ”„๋ ˆ์ž„์›Œํฌ๋ฅผ ์ด์šฉํ•˜๊ธฐ๋กœ ๊ฒฐ์ •ํ–ˆ๋‹ค๋ฉด ์ตœ๋Œ€ํ•œ ํ”„๋ ˆ์ž„์›Œํฌ์˜ ์˜๋„๋œ ์šฉ๋„์— ๋งž๊ฒŒ ์ด์šฉํ•œ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด ์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ๋ฅผ ์ด์šฉํ•˜๋ฉด์„œ ๋ณด์•ˆ ๊ตฌํ˜„์— ํ”„๋ ˆ์ž„์›Œํฌ๊ฐ€ ์ œ๊ณตํ•˜๋Š” ๊ฒƒ๋ณด๋‹ค ๋งž์ถคํ˜• ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๋Š” ์ผ์ด ๋งŽ๋‹ค๊ณ  ๋Š๋‚€๋‹ค๋ฉด ์ด๋Ÿฐ ์ผ์ด ์™œ ์ƒ๊ธฐ๋Š”์ง€ ์˜๋ฌธ์„ ๊ฐ€์ ธ์•ผ ํ•œ๋‹ค.


    SecurityContext ์ด์šฉ
    ๋ณด์•ˆ ์ปจํ…์ŠคํŠธ๋Š” Authentication ๊ฐ์ฒด๋ฅผ ์ €์žฅํ•˜๋Š” ์ธ์Šคํ„ด์Šค์ž…๋‹ˆ๋‹ค.

    public interface SecurityContext extends Serializable{
    	Authentication getAuthentication();
    	void setAuthentication(Authentication authentication);
    }
    

    SecurityContext๋ฅผ ๊ด€๋ฆฌํ•˜๊ธฐ ์œ„ํ•ด ์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ๋Š” SecurityContextHolder๋ผ๋Š” ๊ฐ์ฒด๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. MODE_THREADLOCAL, MODE_INHRITABLETHREADLOCAL, MODE_GLOBAL ์˜ต์…˜์„ ์ œ๊ณตํ•˜๊ณ  ์„ค์ •์€ Config์—์„œ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

    return() -> SecurityContextHolder.setStrategyName(	SecurityContextHodler.MODE_INHERITABLETHREADLOCAL);
    

    MODE_THREADLOCAL
    (๋ณด์•ˆ ์ปจํ…์ŠคํŠธ๋ฅผ ์œ„ํ•œ ๋ณด์œ  ์ „๋žต ์ด์šฉ)
    ๊ฐ ์Šค๋ ˆ๋“œ๊ฐ€ ๋ณด์•ˆ ์ปจํ…์ŠคํŠธ ๊ฐ์ž ์„ธ๋ถ€ ์ •๋ณด๋ฅผ ์ €์žฅํ•˜๋Š” ๊ฒƒ์œผ๋กœ ๊ธฐ๋ณธ๊ฐ’์ž…๋‹ˆ๋‹ค. ์ƒˆ ์Šค๋ ˆ๋“œ๋„ ์ž์ฒด ๋ณด์•ˆ ์ปจํ…์ŠคํŠธ๋ฅผ ๊ฐ€์ง€๋ฉฐ ์ƒ์œ„ ์Šค๋ ˆ๋“œ์˜ ์„ธ๋ถ€ ์ •๋ณด๊ฐ€ ์ƒˆ ์Šค๋ ˆ๋“œ์˜ ๋ณด์•ˆ ์ปจํ…์ŠคํŠธ๋กœ ๋ณต์‚ฌ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์ด SecurityContext์—์„œ Authentication์„ ์–ป๊ธฐ ์œ„ํ•ด์„œ๋Š” ์•ค๋“œํฌ์ธํŠธ ๋งค๊ฐœ๋ณ€์ˆ˜์— ๋ฐ”๋กœ ์ฃผ์ž…ํ•ด์„œ ์–ป์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

    @GetMapping
    public String hello(Authentication a){
    	return a.getName();
    }
    

    MODE_INHRITABLETHREADLOCAL
    (๋น„๋™๊ธฐ ํ˜ธ์ถœ์„ ์œ„ํ•œ ๋ณด์œ  ์ „๋žต ์ด์šฉ)
    MODE_THREADLOCAL์™€ ๋น„์Šทํ•˜์ง€๋งŒ ๋น„๋™๊ธฐ ๋ฉ”์„œ๋“œ์˜ ๊ฒฝ์šฐ ๋ณด์•ˆ ์ปจํ…์ŠคํŠธ๋ฅผ ๋‹ค์Œ ์Šค๋ ˆ๋“œ๋กœ ๋ณต์‚ฌํ•˜๋„๋ก ์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ์— ์ง€์‹œํ•ฉ๋‹ˆ๋‹ค. ๋ถ€๋ชจ ์Šค๋ ˆ๋“œ์—์„œ ์ƒ์„ฑ๋œ SecurityContext๋ฅผ ์ž์‹ ์Šค๋ ˆ๋“œ์—์„œ๋„ ๊ณต์œ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ฆ‰, ์›๋ž˜ ์Šค๋ ˆ๋“œ์— ์žˆ๋Š” ์„ธ๋ถ€ ์ •๋ณด๋ฅผ ๋น„๋™๊ธฐ ๋ฉ”์„œ๋“œ์˜ ์ƒˆ๋กœ ์ƒ์„ฑ๋œ ์Šค๋ ˆ๋“œ๋กœ ๋ณต์‚ฌํ•ฉ๋‹ˆ๋‹ค.

    MODE_GLOBAL
    (๋…๋ฆฝํ˜• ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์œ„ํ•œ ๋ณด์œ  ์ „๋žต ์ด์šฉ)
    ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๋ชจ๋“  ์Šค๋ ˆ๋“œ๊ฐ€ ๊ฐ™์€ ๋ณด์•ˆ ์ปจํ…์ŠคํŠธ ์ธ์Šคํ„ด์Šค๋ฅผ ๋ณด๊ฒŒ ํ•ฉ๋‹ˆ๋‹ค. ์Šค๋ ˆ๋“œ ์•ˆ์ „์„ ์ง€์›ํ•˜์ง€ ์•Š์Œ์— ์œ ์˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๊ณต์œ  ์Šค๋ ˆ๋“œ ์ „๋žต์—์„œ๋Š” ์Šคํ”„๋ง์ด ๊ด€๋ฆฌํ•˜๋Š” ์Šค๋ ˆ๋“œ์—๋งŒ ์ „๋žต์ด ์ ์šฉ๋œ๋‹ค๋Š” ๊ฒƒ์„ ๊ธฐ์–ตํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.


    ๋ณด์•ˆ ์ปจํ…์ŠคํŠธ๋ฅผ ์ƒˆ๋กœ ์ƒ์„ฑํ•œ ์Šค๋ ˆ๋“œ๋กœ ์ „ํŒŒํ•˜๊ฒŒ ๋„์™€์ฃผ๋Š” ๋ช‡ ๊ฐ€์ง€ ์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ์˜ ์œ ํ‹ธ๋ฆฌํ‹ฐ ํˆด์ด ์žˆ์Šต๋‹ˆ๋‹ค.

    DelegatingSecurityContextRunnable
    Runnable ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ตฌํ˜„ํ•œ ํด๋ž˜์Šค๋กœ, ์ƒ์„ฑ์ž๋กœ Runnable ๊ฐ์ฒด์™€ ํ˜„์žฌ SecurityContext๋ฅผ ๋ฐ›์•„์„œ, ์ƒˆ๋กœ ์ƒ์„ฑํ•œ ์Šค๋ ˆ๋“œ์—์„œ Runnable ๊ฐ์ฒด๋ฅผ ์‹คํ–‰ํ•  ๋•Œ ๋ณด์•ˆ ์ปจํ…์ŠคํŠธ๋ฅผ ์ „ํŒŒํ•ฉ๋‹ˆ๋‹ค. ๋ฐ˜ํ™˜๊ฐ’์ด ์—†๋Š” ์ž‘์—… ์‹คํ–‰ ํ›„ ์ด์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

    SecurityContext securityContext = SecurityContextHolder.getContext();
    Runnable runnable = new MyRunnable();
    Runnable wrappedRunnable = new DelegatingSecurityContextRunnable(runnable, securityContext);
    new Thread(wrappedRunnable).start();
    

    DelegatingSecurityContextCallable
    ๋ฐ˜ํ™˜๊ฐ’์ด ์žˆ๋Š” ์ž‘์—…์—๋Š” DelegatingSecurityContextCallable ๋Œ€์•ˆ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ˜„์žฌ ๋ณด์•ˆ ์ปจํ…์ŠคํŠธ๋ฅผ ๋ณต์‚ฌํ•ด ๋น„๋™๊ธฐ์ ์œผ๋กœ ์‹คํ–‰ํ•  Callable ์ž‘์—…์„ ์ง€์ •ํ•ฉ๋‹ˆ๋‹ค.

    DelegatingSecurityContextExecutorService
    ExecutorService ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ตฌํ˜„ํ•œ ํด๋ž˜์Šค๋กœ, ๋ณด์•ˆ ์ปจํ…์ŠคํŠธ๋ฅผ ์ƒˆ๋กœ ์ƒ์„ฑํ•œ ์Šค๋ ˆ๋“œ์—์„œ ์‹คํ–‰ํ•˜๋Š” ์ž‘์—…์„ ์ž๋™์œผ๋กœ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด์™€ ์œ ์‚ฌํ•˜๊ฒŒ DelegatingSecurityContextScheduledExecutorService๋Š” ์˜ˆ์•ฝ๋œ ์ž‘์—…์„ ์œ„ํ•ด ๋ณด์•ˆ ์ปจํ…์ŠคํŠธ ์ „ํŒŒ๋ฅผ ๊ตฌํ˜„ํ•ด์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ์˜ ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋กœ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.

    SecurityContext securityContext = SecurityContextHolder.getContext();
    ExecutorService executorService = Executors.newFixedThreadPool(10);
    ExecutorService wrappedExecutorService = new DelegatingSecurityContextExecutorService(executorService, securityContext);
    wrappedExecutorService.submit(new MyRunnable());
    


    HTTP Basic ์ธ์ฆ ์–‘์‹

    http.httpBasic(c->{
    		// ์˜์—ญ ์ด๋ฆ„์„ ๋ณ€๊ฒฝํ•˜๋Š” ๋ฐฉ๋ฒ•
    		c.realName("OTHER");
    		// custom AuthenticationEntryPoint ์ ์šฉ
    		c.authenticationEntiryPoint(new CustomEntryPoint());
    	})
    	http.authorizeRequests().anyRequest().authenticated();
    

    AuthenticationEntryPoint ์ธํ„ฐํŽ˜์ด์Šค
    Spring Security์—์„œ๋Š” ์—ฌ๋Ÿฌ ๊ฐ€์ง€ AuthenticationEntryPoint ๊ตฌํ˜„์ฒด๋ฅผ ์ œ๊ณตํ•˜๋ฉฐ, ์ด๋ฅผ ํ†ตํ•ด ๋‹ค์–‘ํ•œ ์ธ์ฆ ์‹คํŒจ ์‹œ๋‚˜๋ฆฌ์˜ค์— ๋Œ€์‘ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ๋Š” LoginUrlAuthenticationEntryPoint๊ฐ€ ์ž์ฃผ ์‚ฌ์šฉ๋˜๋ฉฐ, REST API์—์„œ๋Š” Http403ForbiddenEntryPoint๊ฐ€ ์ž์ฃผ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.

    ์–‘์‹ ๊ธฐ๋ฐ˜ ๋กœ๊ทธ์ธ ์ธ์ฆ

    ์ˆ˜ํ‰์  ํ™•์žฅ์„ฑ์ด ํ•„์š”ํ•œ ๋Œ€ํ˜• ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ๋ณด์•ˆ ์ปจํ…์ŠคํŠธ๋ฅผ ๊ด€๋ฆฌํ•˜๋Š” ๋ฐ ์„œ๋ฒ„ ์ชฝ ์„ธ์…˜์„ ์ด์šฉํ•˜๋Š” ๊ฒƒ์€ ์ข‹์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

    ์–‘์‹ ๊ธฐ๋ฐ˜ ๋กœ๊ทธ์ธ์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ์—๋Š” formLogin() ๋ฉ”์„œ๋“œ๋ฅผ ์ด์šฉํ•ฉ๋‹ˆ๋‹ค. ์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ์—์„œ ๋กœ๊ทธ์ธ์„ ํ•˜์ง€ ์•Š๋Š” ๊ฒฝ์šฐ๋Š” ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€๋กœ ๋ฆฌ๋””๋ ‰์…˜๋˜๊ฒŒ ์ฒ˜๋ฆฌ๋ฉ๋‹ˆ๋‹ค.

    http.formLogin();
    

    @RestController๊ฐ€ ์•„๋‹Œ @Controller๋ฅผ ์‚ฌ์šฉํ•˜๋ฏ€๋กœ์จ ๋ฉ”์„œ๋“œ ๋ฐ˜ํ™˜ ๊ฐ’์„ HTTP ์‘๋‹ต์œผ๋กœ ๋ณด๋‚ด๋Š” ๊ฒƒ์ด ์•„๋‹Œ home.html๋กœ ๋žœ๋”๋งํ•ด์ค๋‹ˆ๋‹ค. ์ด ๋•Œ ๋กœ๊ทธ์ธ์„ ํ•˜์ง€ ์•Š์•˜๋‹ค๋ฉด, /login ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€๋กœ ๋ฆฌ๋””๋ ‰์…˜๋˜๊ณ  /logout ๊ฒฝ๋กœ๋กœ ์ ‘๊ทผํ•˜๋ฉด ๋กœ๊ทธ์•„์›ƒ ํŽ˜์ด์ง€๋กœ ๋ฆฌ๋””๋ ‰์…˜๋ฉ๋‹ˆ๋‹ค. (HTTP 302)

    @Controller
    public class HelloController{
    	@Getmapping("/home")
    	public String home(){ return "home.html"; }
    }
    

    formLogin()์˜ ๋งž์ถค ๊ตฌ์„ฑ์„ ์œ„ํ•ด AuthenticationSuccessHandler์™€ AuthenticationFailureHandler ๊ฐ์ฒด๋ฅผ ์ด์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.



    6์žฅ ์‹ค์ „: ์ž‘๊ณ  ์•ˆ์ „ํ•œ ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜

    ์ฃผ๋กœ ๊ตฌํ˜„๋‹จ์— ๋Œ€ํ•œ ์ด์•ผ๊ธฐ๊ฐ€ ์žˆ๋Š” ์ฑ•ํ„ฐ์ž…๋‹ˆ๋‹ค. ์ž‘์„ฑ์ผ์ž ๊ธฐ์ค€ Deprecated๋œ ๋‚ด์šฉ์€ ์ œ์™ธํ–ˆ์Šต๋‹ˆ๋‹ค.

    • ์˜์กด์„ฑ ์ถ”๊ฐ€
      • SQL ๋ฒ„์ „ ์ง€์ •์„ ์œ„ํ•œ ํ”Œ๋ผ์ด์›จ์ด, ๋ฆฌํ€ด๋ฒ ์ด์Šค ์ข…์†์„ฑ ์ด์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
    • ๋น„๋ฐ€๋ฒˆํ˜ธ ์•”ํ˜ธํ™”๋ฅผ ์œ„ํ•œ PasswordEncoder @Bean์œผ๋กœ ๋“ฑ๋ก
    • User, Authority ์—”ํ‹ฐํ‹ฐ ์„ค์ •
    • UserDetails ์ธํ„ฐํŽ˜์ด์Šค์˜ ๊ตฌํ˜„
    @Override
    public CustomUserDetails loadUserByUsername(String username){
    	Supplier<UsernameNotFoundException> s = () -> new UsernameNotFoundException("user not found");
    	User user  = userRepository.findByUsername(username)
    	.orElseThrow(s);
    	return new CustomUserDetails(user);
    }
    

    ์ธ์ฆ๋…ผ๋ฆฌ์—์„œ ์•”ํ˜ธ๊ฐ€ ์ผ์น˜ํ•˜๋ฉด encoder.matches(rawPassword, user.getPassword()) ์ธ์ฆ์ด ๋˜์—ˆ์œผ๋ฏ€๋กœ, Authentication์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.

    ๋Œ€๋ถ€๋ถ„์€ ๊ฐ™์€ ๊ธฐ๋Šฅ์„ ์—ฌ๋Ÿฌ ๊ฐ€์ง€ ๋‹ค๋ฅธ ๋ฐฉ๋ฒ•์œผ๋กœ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ๊ฐ€์žฅ ๋‹จ์ˆœํ•œ ํ•ด๊ฒฐ์ฑ…์„ ์„ ํƒํ•ด ์ฝ”๋“œ๋ฅผ ์ดํ•ดํ•˜๊ธฐ ์‰ฝ๊ฒŒ ๋งŒ๋“ค์–ด ์˜ค๋ฅ˜์™€ ๋ณด์•ˆ ์นจํ•ด ์—ฌ์ง€๋ฅผ ์ค„์ผ ํ•„์š”๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.



    7์žฅ. ๊ถŒํ•œ ๋ถ€์—ฌ ๊ตฌ์„ฑ: ์•ก์„ธ์Šค ์ œํ•œ

    ์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ์—์„œ๋Š” ์ธ์ฆ ํ•„ํ„ฐ์—์„œ ๋ณด์•ˆ ์ปจํ…์ŠคํŠธ์— ์ธ์ฆ๋œ ์‚ฌ์šฉ์ž ์„ธ๋ถ€ ์ •๋ณด๋ฅผ ์ €์žฅํ•˜๊ณ  ์ดํ›„์— ๊ถŒํ•œ ๋ถ€์—ฌ ํ•„ํ„ฐ๋กœ ์š”์ฒญ์„ ํ—ˆ์šฉํ• ์ง€ ๊ฒฐ์ •ํ•ฉ๋‹ˆ๋‹ค.

    ํ•œ ์‚ฌ์šฉ์ž๋Š” ํ•˜๋‚˜ ์ด์ƒ์˜ ๊ถŒํ•œ์„ ๊ฐ–์Šต๋‹ˆ๋‹ค. ์ฆ‰ GrantedAuthority๋กœ UserDetils๋Š” ํ•˜๋‚˜ ์ด์ƒ์˜ ๊ถŒํ•œ์„ ๊ฐ–์Šต๋‹ˆ๋‹ค. UserDetils์˜ getAuthorities() ๋ฉ”์„œ๋“œ๋กœ ์‚ฌ์šฉ์ž ์„ธ๋ถ€ ์ •๋ณด์˜ ๋ชจ๋“  ๊ถŒํ•œ์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.

    ์—”๋“œํฌ์ธํŠธ ๊ถŒํ•œ ์ œ์–ด
    permitAll()์ด ์•„๋‹Œ ํŠน์ • ๊ถŒํ•œ ์ œ์–ด๋กœ โ€œWRITEโ€ ๊ถŒํ•œ์„ ๊ฐ€์ง„ ๊ฒฝ์šฐ์—๋งŒ ์ ‘๊ทผ์ด ๊ฐ€๋Šฅํ•˜๋„๋ก ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด์™ธ์—๋„ ๊ถŒํ•œ ์ œ์–ด๋ฅผ ์œ„ํ•œ ๋‹ค์Œ ์„ธ ๊ฐ€์ง€ ๋ฉ”์„œ๋“œ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.

    • hasAuthority()
    • hasAnyAuthority()
    • access()
    // .hasAuthority()์˜ ์ด์šฉ
    http.authoriseRequests()
    .anyRequest()
    .hasAuthority("WRITE");
    
    // .access()์˜ ์ด์šฉ
    // ๋ณต์žกํ•œ ์‹์„ ์ž‘์„ฑํ•ด์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ์˜ ์‹ค์ œ ์‹œ๋‚˜๋ฆฌ์˜ค๊ฐ€ ์žˆ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
    http.authoriseRequests()
    .anyRequest()
    .access("hasAuthority('WRITE') and !hasAuthority('DELETE')");
    

    ์Šคํ”„๋ง ์‹(SpEL)์ด๋ž€?
    Spring Framework์—์„œ ์ œ๊ณตํ•˜๋Š” ํ‘œํ˜„ ์–ธ์–ด์ž…๋‹ˆ๋‹ค.
    ์Šคํ”„๋ง ์‹์€ XML์ด๋‚˜ ์• ๋…ธํ…Œ์ด์…˜ ๊ธฐ๋ฐ˜์˜ ๊ตฌ์„ฑ์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ๊ฐ์ฒด ๊ทธ๋ž˜ํ”„๋ฅผ ํƒ์ƒ‰ํ•˜๊ณ  ์กฐ์ž‘ํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. ์Šคํ”„๋ง ์‹์€ Java์˜ ๋‹ค์–‘ํ•œ ์—ฐ์‚ฐ์ž์™€ ํ•จ์ˆ˜๋ฅผ ์ง€์›ํ•˜๋ฉฐ, ๊ฐ์ฒด์˜ ํ”„๋กœํผํ‹ฐ๋‚˜ ๋ฉ”์„œ๋“œ ํ˜ธ์ถœ, ๋ฆฌ์ŠคํŠธ๋‚˜ ๋งต์˜ ์ธ๋ฑ์Šค ์ ‘๊ทผ, ์‚ฐ์ˆ  ์—ฐ์‚ฐ ๋“ฑ ๋‹ค์–‘ํ•œ ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ์ฑ…์˜ ์˜ˆ์ œ์—์„œ๋Š” ์ •์˜ค ์ดํ›„์—๋งŒ ์—”๋“œํฌ์ธํŠธ ์ ‘๊ทผ์„ ํ•˜์šฉํ•˜๋Š” ๊ฒฝ์šฐ ๋“ฑ SpEL ์‹์„ ํ™œ์šฉํ•ด์„œ access() ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋” ๋ฒ”์šฉ์ ์ธ ์ƒํ™ฉ์—์„œ ์‚ฌ์šฉ์ด ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.

    ๊ถŒํ•œ์ด ์—†๋Š” ๊ฒฝ์šฐ์˜ ์š”์ฒญ์€ HTTP ์ƒํƒœ๋Š” HTTP 403 Forbidden์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์„œ ๊ถŒํ•œ๊ณผ ๋‹ฌ๋ฆฌ ์—ญํ• ์€ ๊ถŒํ•œ๋ณด๋‹ค ๊ฒฐ์ด ๊ตต์€๋ฐ ๊ฐ™์€ GrantedAuthority๊ฐ€ ์‚ฌ์šฉ๋˜๋ฉฐ, ์—ญํ• ์ด๋ฆ„์€ ROLE_๋กœ ์‹œ์ž‘ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

    User.withUsername("john")
    .password("qwer1234")
    .authorities("ROLE_MEMBER")
    .build();
    

    permitAll()๋กœ ๋ชจ๋“  ์ ‘๊ทผ ๊ถŒํ•œ์„ ํ—ˆ์šฉํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, denyAll()๋กœ ๋ชจ๋“  ์š”์ฒญ์„ ๊ฑฐ๋ถ€ํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.



    8์žฅ. ๊ถŒํ•œ ๋ถ€์—ฌ ๊ตฌ์„ฑ: ์ œํ•œ ์ ์šฉ

    ์„ ํƒ๊ธฐ ๋ฉ”์„œ๋“œ๋กœ ์—”๋“œํฌ์ธํŠธ ์„ ํƒ
    ์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ์—์„œ ์ œ๊ณตํ•˜๋Š” ์„ ํƒ๊ธฐ ๋ฉ”์„œ๋“œ๋Š” MVC ์„ ํƒ๊ธฐ, ์•คํŠธ ์„ ํƒ๊ธฐ, ์ •๊ทœ์‹ ์„ ํƒ๊ธฐ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.

    ์ด์šฉํ•˜๋Š” ์„ ํƒ๊ธฐ๊ฐ€ ์–ด๋–ค ๊ฒƒ์ด์ง€๋„ ๋ชจ๋ฅด๊ณ  ํ•˜๋Š” ๋ณต์‚ฌ-๋ถ™์—ฌ๋„ฃ๊ธฐ ํ”„๋กœ๊ทธ๋ž˜๋ฐ์˜ ์œ„ํ—˜ํ•œ ์ ‘๊ทผ๋ฒ•์„ ์ดˆ๋ณด ๊ฐœ๋ฐœ์ž๊ฐ€ ๋„ˆ๋ฌด ์ž์ฃผ ์ด์šฉํ•ฉ๋‹ˆ๋‹ค.
    ์–ด๋–ป๊ฒŒ ์ž‘๋™ํ•˜๋Š”์ง€ ์ดํ•ดํ•˜๊ธฐ ์ „์—๋Š” ์ด์šฉํ•˜์ง€ ๋ง์•„์•ผ ํ•ฉ๋‹ˆ๋‹ค!

    MVC ์„ ํƒ๊ธฐ
    ์ฑ…์—์„œ๋Š” ์•คํŠธ ์„ ํƒ๊ธฐ๋ณด๋‹ค MVC ์„ ํƒ์„ ๊ถŒ์žฅํ–ˆ๋Š”๋ฐ, ๋งคํ•‘์œผ๋กœ ์ธํ•œ ์œ„ํ—˜์„ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•จ์ž…๋‹ˆ๋‹ค.

    http.authorizeRequests()
            .mvcMatchers(HttpMethod.Post, "/hello").authenticated()
            // ๊ธธ์ด์™€ ๊ด€๊ณ„์—†์ด ์ˆซ์ž๋ฅผ ํฌํ•จํ•˜๋Š” ๋ฌธ์ž์—ด์„ ๋‚˜ํƒ€๋‚ด๋Š” ์ •๊ทœ์‹
            .mvcMatchers("/reg/{code:^[0-9]*$*}").permitAll()
            .anyRequest().authenticated();
    

    ์•คํŠธ ์„ ํƒ๊ธฐ
    MVC ์„ ํƒ๊ธฐ์™€ ๋‹ฌ๋ฆฌ ์Šคํ”„๋ง MVC์˜ ์ž‘๋™์„ ๊ณ ๋ คํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹Œ ๋ฐฉ์‹์œผ๋กœ /hello ๊ฒฝ๋กœ์˜ ์—”๋“œํฌ์ธํŠธ๋ฅผ ์„ค์ •ํ•œ๋‹ค๊ณ  ํ–ˆ์„ ๋•Œ, /hello/ ๊ฒฝ๋กœ๋Š” ๋ณดํ˜ธ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์ฆ‰ ํ™•์‹คํ•œ ๊ฒฝ๋กœ ์„ค์ •์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

    http.authorizeRequests()
            .antMatchers("/hello/**").hasRole("ADMIN")
            .anyRequest().authenticated();        
    

    ์ •๊ทœ์‹ ์„ ํƒ๊ธฐ
    ์˜จ๋ผ์ธ ์ •๊ทœ์‹ ์„ ํƒ๊ธฐ
    MVC์™€ ์—”ํŠธ ์‹์œผ๋กœ ํ•ด๊ฒฐํ•  ์ˆ˜ ์—†๋Š” ๊ฒฝ์šฐ์— ์ด์šฉํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค.

    http.authorizeRequests()
            .regexMatchers(".*/(kr|ca)+/(en).**").authenticated()
            .anyRequest().authenticated();        
    

    ์ž˜๋ชป๋œ ์ž๊ฒฉ ์ฆ๋ช…์œผ๋กœ ์—”๋“œํฌ์ธํŠธ๋ฅผ ํ˜ธ์ถœํ•˜๋ฉด ๊ถŒํ•œ์ด ์žˆ๋”๋ผ๋„ ์ธ์ฆ ํ•„ํ„ฐ์—์„œ ๋จผ์ € ์ธ์ฆ์ด ์‹คํŒจํ•˜๋Š” ๊ฒฝ์šฐ ๊ถŒํ•œ๋ถ€์—ฌ ํ•„ํ„ฐ๊นŒ์ง€ ๊ฐ€์ง€ ์•Š๊ณ  ์‘๋‹ต ์ƒํƒœ HTTP 401 Unauthorized๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.

    ์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ๋ฅผ ๊ธฐ๋ณธ์ ์œผ๋กœ CSRF(์‚ฌ์ดํŠธ ๊ฐ„ ์š”์ฒญ ์œ„์กฐ)์— ๋Œ€ํ•œ ๋ณดํ˜ธ๋ฅผ ์ ์šฉํ•˜๋Š”๋ฐ ๋ชจ๋“  ์—”๋“œํฌ์ธํŠธ ํ˜ธ์ถœ์„ ์œ„ํ•ด ๋น„ํ™œ์„ฑํ™”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

    http.csrf().disable();
    



    9์žฅ. ํ•„ํ„ฐ ๊ตฌํ˜„

    ์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ๊ฐ€ ์ œ๊ณตํ•˜๋Š” ํ•„ํ„ฐ BasicAuthenticationFilter ์™ธ์˜ ๋งž์ถคํ˜• ํ•„ํ„ฐ ์ฒด์ธ์„ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ถŒํ•œํ•„ํ„ฐ ์ „ ์ด๋ฒคํŠธ๋‚˜ ๋กœ๊น…ํ•„ํ„ฐ ๊ตฌ์ถ•์ด ๊ฐ€๋Šฅํ•ด์ง‘๋‹ˆ๋‹ค. ์Šคํ”„๋ง์—์„œ ์ œ๊ณตํ•˜๋Š” ํ•„ํ„ฐ ๊ฐ„์—๋Š” ์ˆœ์„œ๊ฐ€ ์ด๋ฏธ ์ •ํ•ด์ ธ ์žˆ์Šต๋‹ˆ๋‹ค. CorsFilter โ†’ CsrfFilter โ†’ BasicAuthenticationFilter ์ˆœ์ž…๋‹ˆ๋‹ค.

    ๋งž์ถคํ˜• ํ•„ํ„ฐ ๊ตฌํ˜„

    public class RequestVaildationFilter implements Filter {
    	@Override
    	public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {  
    		var httpRequest = (HttpServletRequest) request;
    		var httpResponse = (HttpServletResponse) response;
    		String requestId = httpRequest.getHeader("Request_id");
    
    		if(requestId.isBlank){
    			httpResponse.setStatus(HttpServletResponse.SC_BAD_REQUEST);
    			return;
    		}
    		filterChain.doFilter(request, response);
    	}
    }
    

    ๋งž์ถคํ˜• ํ•„ํ„ฐ ์ˆœ์„œ ์ง€์ •
    ์ด๋ ‡๊ฒŒ addFilterBefore๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด, BasicAuthenticationFilter ์ „์— RequestVaildationFilter๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ ๋ฉ๋‹ˆ๋‹ค.
    ์ธ์ฆ ํ†ต๊ณผ๋ฅผ ๊ธฐ๋กํ•˜๋Š” ํ•„ํ„ฐ๋ฅผ ๋งŒ๋“ ๋‹ค๊ณ  ํ•œ๋‹ค๋ฉด BasicAuthenticationFilter ๋’ค์— ๋ฐฐ์น˜ํ•  ์ˆ˜ ์žˆ๋„๋ก addFilterAfter๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

    http.addFilterBefore(
    	new RequestVaildationFilter(),
    	BasicAuthenticationFilter.class)
    	.authorizeRequests().anyRequest().permitAll();
    )
    

    ๊ทธ๋ ‡๋‹ค๋ฉด ๋‹ค๋ฅธ ํ•„ํ„ฐ ์œ„์น˜์— ํ•„ํ„ฐ๋ฅผ ์ถ”๊ฐ€ํ•˜๋ ค๋ฉด ์–ด๋–ป๊ฒŒ ํ•ด์•ผํ• ๊นŒ?
    BasicAuthenticationFilter์— ๋ฐฐ์น˜ํ•˜๋ ค๋ฉด addFilterAt๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Š” ํ•„ํ„ฐ ๋Œ€์ฒด๊ฐ€ ์•„๋‹Œ ์ถ”๊ฐ€์ ์ธ ๊ฐœ๋…์œผ๋กœ ๋ด์•ผํ•ฉ๋‹ˆ๋‹ค.

    ์ด๋Ÿฐ ๊ฒฝ์šฐ์˜ ์‹œ๋‚˜๋ฆฌ์˜ค์— ๋Œ€ํ•ด์„œ ์ฑ…์—์„œ๋Š” ๋‹ค์Œ ์„ธ ๊ฐ€์ง€ ์‚ฌ๋ก€๋ฅผ ๋“ค๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

    • ์ธ์ฆ์€ ์œ„ํ•œ ์ •์  ํ—ค๋” ๊ฐ’์— ๊ธฐ๋ฐ˜์„ ๋‘” ์‹๋ณ„
      • ์•”ํ˜ธํ™” ์„œ๋ช…์ด ์•„๋‹Œ ๋‹จ์ˆœํ•œ ๋ฌธ์ž์—ด ์ œ๊ณต์œผ๋กœ ์ธ์ฆํ•˜๋Š” ๊ฒฝ์šฐ
    • ๋Œ€์นญ ํ‚ค๋ฅผ ์ด์šฉํ•ด ์ธ์ฆ ์š”์ฒญ ์„œ๋ช…
      • ํด๋ผ์ด์–ธํŠธ์™€ ์„œ๋ฒ„๊ฐ€ ํ‚ค๋ฅผ ๊ณต์œ ํ•˜๋Š” ๊ฒฝ์šฐ
    • ์ธ์ฆ ํ”„๋กœ์„ธ์Šค์— OTP ์ด์šฉ
      • ํƒ€๊ฐ€ ์ธ์ฆ ์„œ๋ฒ„์—์„œ OTP๋ฅผ ์–ป์–ด์„œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋‹ค๋‹จ๊ณ„ ์ธ์ฆ์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ

    ์‹ค์ œ ์‹œ๋‚˜๋ฆฌ์˜ค์—์„œ ์„ธ๋ถ€ ์ •๋ณด๋ฅผ ์ €์žฅํ•  ๋•Œ๋Š” ๋น„๋ฐ€ ๋ณผํŠธ๋ฅผ ์ด์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

    UserDetailsService ๊ตฌ์„ฑ์„ ๋น„ํ™œ์„ฑํ™” ํ•˜๊ธฐ ์œ„ํ•ด์„œ exclude ํŠน์„ฑ์„ ์ด์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

    @SpringBootApplication(exclude={UserDetailsServiceAutoConfiguration.class})
    

    OncePerRequestFilter
    ์Šคํ”„๋ง ์‹œํ๋ฆฌํŠธ์—์„œ ์ œ๊ณตํ•˜๋Š” ํ•„ํ„ฐ๋ฅผ ์ด์šฉํ•˜๋Š” ๊ฒƒ๋„ ์ข‹์ง€๋งŒ ์ตœ๋Œ€ํ•œ ๊ฐ„๋‹จํ•˜๊ฒŒ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•์ด ์žˆ๋‹ค๋ฉด ๊ทธ๋ƒฅ ํ™•์žฅํ•˜๋Š” ๊ฒƒ์„ ํ”ผํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๊ฐ™์€ ์š”์ฒญ์— ํ•œ ๋ฒˆ์˜ ํ•„ํ„ฐ ํ˜ธ์ถœ์„ ์œ„ํ•ด OncePerRequestFilter ํด๋ž˜์Šค๋ฅผ ์ด์šฉํ•ด ํ•„ํ„ฐ๋ฅผ ๊ตฌํ˜„ํ•˜๋Š” ๊ฒƒ์ด ๊ถŒ์žฅ๋ฉ๋‹ˆ๋‹ค.

    OncePerRequestFilter๋Š” ํ˜•์‹์„ ํ˜•๋ณ€ํ™˜ํ•˜์—ฌ HttpServletReqeust ๋ฐ HttpServletResponse๋กœ ์ง์ ‘ ์š”์ฒญ์„ ์ˆ˜์‹ ํ•ฉ๋‹ˆ๋‹ค. ํ•„ํ„ฐ์—์„œ ์š”์ฒญ ๋ฐ ์‘๋‹ต์„ ์ง์ ‘ ์กฐ์ž‘ํ•  ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ ํ•„์š”ํ•œ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ๊ฐ์ฒด๋ฅผ ์ง์ ‘ ์‚ฌ์šฉํ•˜๋ฉด ํ•„ํ„ฐ์—์„œ ์™ธ๋ถ€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋‚˜ ์„œ๋น„์Šค์— ์˜์กดํ•˜์ง€ ์•Š์•„๋„ ๋˜๋ฏ€๋กœ ๋…๋ฆฝ์„ฑ๊ณผ ์œ ์—ฐ์„ฑ์„ ๋†’์—ฌ์ค๋‹ˆ๋‹ค.
    ๊ธฐ๋ณธ์ ์œผ๋กœ ๋น„๋™๊ธฐ ์š”์ฒญ์ด๋‚˜ ์˜ค๋ฅ˜ ๋ฐœ์†ก ์š”์ฒญ์—๋Š” ์ ์šฉ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ๋ฉ”์„œ๋“œ ์žฌ์ •์˜๋กœ ๊ฐ€๋Šฅํ•˜๊ฒŒ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ•„ํ„ฐ๊ฐ€ ์ ์šฉ๋ ์ง€ ์—ฌ๋ถ€๋„ ๋ฉ”์„œ๋“œ ์žฌ์ •์˜๋กœ ์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.



    10์žฅ. CSRF ๋ณดํ˜ธ์™€ CORS ์ ์šฉ

    CSRF(์‚ฌ์ดํŠธ ๊ฐ„ ์š”์ฒญ ์œ„์กฐ) ๋ณดํ˜ธ ์ ์šฉ
    POST, PUT, DELETE๋ฅผ ๋น„๋กฏํ•œ ๋ณ€๊ฒฝ ํ˜ธ์ถœ ํŽ˜์ด์ง€๋Š” ์‘๋‹ต์œผ๋กœ CSRF ํ† ํฐ์„ ๋ฐ›๊ณ  ๋‹ค์Œ ํ˜ธ์ถœ์— ์‚ฌ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์„œ CSRF ๊ณต๊ฒฉ์€ ์‚ฌ์šฉ์ž๊ฐ€ ์•…์„ฑ ์Šคํฌ๋ฆฝํŠธ๊ฐ€ ์žˆ๋Š” ํŽ˜์ด์ง€๋ฅผ ์—ด์—ˆ์„ ๋•Œ, ์‚ฌ์šฉ์ž ๋Œ€์‹  ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

    CsrfFilter๋ฅผ ํ†ตํ•ด ๋ฐœ๊ธ‰๋˜๋Š”๋ฐ, ์ด ํ•„ํ„ฐ๋Š” GET, HEAD, TRACE, OPTIONS๋ฅผ ์‚ฌ์šฉํ•œ HTTP ๋ฐฉ์‹์˜ ์š”์ฒญ์„ ๋ชจ๋‘ ํ—ˆ์šฉํ•˜๊ณ  CSRF ํ† ํฐ์„ ๋‹ด์•„์„œ ์‘๋‹ตํ•ฉ๋‹ˆ๋‹ค. POST, PUT, DELETE ์˜ ์—”๋“œํฌ์ธํŠธ์—์„œ ๊ฐœ๋ฐœ์„ ํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” CSRT ๋ณดํ˜ธ ํ™œ์„ฑํ™”๋œ ๊ฒฝ์šฐ ํ† ํฐ์ด ํ•„์š”ํ•œ๋ฐ, _csrf ํŠน์„ฑ์— ์ถ”๊ฐ€๋˜์–ด ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ์ด๋ฅผ ์‘๋‹ต ๋˜๋Š” thymeleaf๋ฅผ ์ด์šฉํ•ด ์ถœ๋ ฅํ•ฉ๋‹ˆ๋‹ค.

    CSRF ๋ณดํ˜ธ๋Š” ๋ธŒ๋ผ์šฐ์ €์—์„œ ์‹คํ–‰๋˜๋Š” ์›น ์•ฑ์— ์‚ฌ์šฉ๋˜๋ฉฐ, ๋กœ๊ทธ์ธ๊ณผ ๊ฐ™์ด ์•ฑ ๋‚ด์—์„œ ์ˆ˜ํ–‰ํ•˜๋Š” ๋ณ€๊ฒฝ๋˜๋Š” ์ž‘์—…์ด ์—†๋Š” ๊ฒฝ์šฐ๋ฅผ ์ œ์™ธํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๊ฐ™์€ ์„œ๋ฒ„๊ฐ€ ํ”„๋ŸฐํŠธ์™€ ๋ฐฑ์„ ๋‹ด๋‹นํ•˜๋Š” ์•„ํ‚คํ…์ฒ˜์—์„œ๋Š” ์ž˜ ์ž‘๋™ํ•˜์ง€๋งŒ ์†”๋ฃจ์…˜์ด ๋…๋ฆฝ์ ์ผ ๋•Œ๋Š” ๊ทธ์— ๋”ฐ๋ฅธ ๋ณด์•ˆ ์ ‘๊ทผ๋ฒ•(11~15์žฅ)์„ ๊ณ ๋ คํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

    Spring security๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ CSRF ๋ณดํ˜ธ๋ฅผ ์ง€์›ํ•˜๋Š”๋ฐ, ์„œ๋ฒ„์—์„œ ์ƒ์„ฑ๋œ ๋ฆฌ์†Œ์Šค๋ฅผ ์ด์šฉํ•˜๋Š” ํŽ˜์ด์ง€๊ฐ€ ๊ฐ™์€ ์„œ๋ฒ„์—์„œ ์ƒ์„ฑ๋œ ๊ฒฝ์šฐ์—๋งŒ ์ด์šฉํ•ฉ๋‹ˆ๋‹ค.

    ๊ทธ๋Ÿผ ๋ชจ๋“  ๊ฒฝ๋กœ๊ฐ€ ์•„๋‹ˆ๋ผ ์ผ๋ถ€ ๊ฒฝ๋กœ์—๋งŒ ๋ณดํ˜ธ๋ฅผ ์„ค์ •ํ•˜๋ ค๋ฉด ์–ด๋–ป๊ฒŒ ํ•ด์•ผ ํ• ๊นŒ? CSRF ๋ณดํ˜ธ๋ฅผ ์•„๋ž˜ ์ฝ”๋“œ ์ฒ˜๋Ÿผ disable ์ฒ˜๋ฆฌํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ ์ œ๊ณต๋˜๋Š” handler์™€ matcher๋“ค ์ด์šฉํ•ด์„œ ๋ณดํ˜ธ ์ œ์™ธ ๊ฒฝ๋กœ๋ฅผ ์ง€์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

    http.httpBasic(HttpBasicConfigurer::disable)  
    	.csrf(CsrfConfigurer::disable)
    

    ์„œ๋ฒ„ ์ชฝ ์„ธ์…˜์— csrf ํ† ํฐ์„ ์ €์žฅํ•˜๋Š” ๊ฒƒ์€ ์ˆ˜ํ‰์  ํ™•์žฅ์—์„œ ์ ํ•ฉํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์—์„œ ํ† ํฐ์„ ๊ด€๋ฆฌํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ์ „ํ™˜ํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” CsrfTokenRepository๋ฅผ ์ƒˆ๋กœ ๊ตฌํ˜„ํ•ด์„œ ์„ธ์…˜ ID๋ฅผ ์ด์šฉํ•ด ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. CsrtTokenRepository ๊ตฌํ˜„์ฒด๋ฅผ ๋งŒ๋“ค๋ฉด override๋œ generateToken()๊ณผ saveToken(), loadToken() ๋ฉ”์„œ๋“œ๋ฅผ ๊ตฌํ˜„ํ•ด์„œ ์‚ฌ์šฉํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ๋‹ค๋ฅธ ๋Œ€์•ˆ์œผ๋กœ๋Š” ํ† ํฐ์˜ ๋งŒ๋ฃŒ์‹œ๊ฐ„์„ ์ •ํ•ด์„œ ๊ด€๋ฆฌํ•˜๋Š” ๋ฐฉ๋ฒ•๋„ ์žˆ์Šต๋‹ˆ๋‹ค.

    CORS(๊ต์ฐจ ์ถœ์ฒ˜ ๋ฆฌ์†Œ์Šค ๊ณต์œ ) ์ด์šฉ
    CORS๋Š” ์ผ๋ถ€ ์กฐ๊ฑด์—์„œ ์„œ๋กœ ๋‹ค๋ฅธ ์ถœ์ฒ˜ ๊ฐ„ ์š”์ฒญ์„ ํ—ˆ์šฉํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด domain.com์—์„œ domain2.com์—์„œ domain.com์˜ REST ์—”๋“œํฌ์ธํŠธ๋ฅผ ํ˜ธ์ถœํ•œ๋‹ค๊ณ  ํ•  ๋•Œ, ํ˜ธ์ถœ์€ ๊ฑฐ๋ถ€๋ฉ๋‹ˆ๋‹ค. CORS๋ฅผ ์ด์šฉํ•˜๋ฉด ์ด๋Ÿฐ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

    • Access-Control-Allow-Origin ์ ‘๊ทผ ๊ฐ€๋Šฅํ•œ ์™ธ๋ถ€ ๋„๋ฉ”์ธ ์ง€์ •
    • Access-Control-Allow-Methods ๋‹ค๋ฅธ ๋„๋ฉ”์ธ์— ๋Œ€ํ•œ ์ ‘๊ทผ์€ ํ—ˆ์šฉํ•˜์ง€๋งŒ ํŠน์ • ์—”๋“œํฌ์ธํŠธ๋งŒ ํ—ˆ์šฉ
    • Access-Control-Allow-Headers ํŠน์ • ์š”์ฒญ์— ์ด์šฉํ•  ์ˆ˜ ์žˆ๋Š” ํ—ค๋”์— ์ œํ•œ์„ ์ถ”๊ฐ€

    CSRF์™€ ๊ฐœ๋…์„ ํ—ท๊ฐˆ๋ฆฌ์ง€ ๋ง์•„์•ผํ•˜๋Š”๋ฐ CORS๋Š” ๊ต์ฐจ ๋„๋ฉ”์ธ ํ˜ธ์ถœ์„ ์™„ํ™”ํ•ด์ฃผ๋Š” ๊ฐœ๋…์ด๊ณ  ๋ธŒ๋ผ์šฐ์ €์— ๊ด€ํ•œ ๊ฒƒ์ด๋ฉฐ ์—”๋“œํฌ์ธํŠธ๋ฅผ ๋ณดํ˜ธํ•˜๋Š” ๋ฐฉ๋ฒ•์€ ์•„๋‹™๋‹ˆ๋‹ค. CSRF ๋ณดํ๋Š” ๊ณต๊ฒฉ์„ ๋ง‰๋Š” ๊ฐœ๋…์ž…๋‹ˆ๋‹ค.

    HTTP OPTIONS ๋ฐฉ์‹์œผ๋กœ ์‚ฌ์ „ ํ…Œ์ŠคํŠธ ์š”์ฒญ์„ ํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ์š”์ฒญ์ด ์‹คํŒจํ•˜๋ฉด ๋ธŒ๋ผ์šฐ์ €๋Š” ์›๋ž˜ ์š”์ฒญ์„ ์ˆ˜๋ฝํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

    CSRF์˜ ์ •์ฑ…์€ @CrossOrigin์œผ๋กœ ์ ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๋ฉ”์„œ๋“œ ์œ„์— ์ ์šฉํ•˜๋ฉด ํ•ด๋‹น ๋ฉ”์„œ๋“œ๋Š” localhost ์ถœ์ฒ˜์— ๋Œ€ํ•œ ๊ต์ฐจ ์ถœ์ฒ˜ ์š”์ฒญ์„ ํ—ˆ์šฉํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ์ด ๋•Œ *๋กœ ๋„“์€ ๋ฒ”์œ„์˜ ์ถœ์ฒ˜๋ฅผ ํ—ˆ์šฉํ•˜๋Š” ๊ฒฝ์šฐ๋Š” XXS(๊ต์ฐจ ์‚ฌ์ดํŠธ ์Šคํฌ๋ฆฝํŒ…) ์š”์ฒญ์— ๋…ธ์ถœ๋˜์–ด DDoS ๊ณต๊ฒฉ์— ์ทจ์•ฝํ•ด์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

    @CrossOrigin("http://localhost:8080")
    

    ํ•˜์ง€๋งŒ ๊ฐœ๋ณ„ ๊ด€๋ฆฌ๋ฅผ ์ด๋Ÿฐ ์‹์œผ๋กœ ํ•˜๊ฒŒ ๋˜๋ฉด, ๊ด€๋ฆฌ๊ฐ€ ์–ด๋ ต๊ธฐ ๋•Œ๋ฌธ์— Security Config์— CorsConfigurationSource๋ฅผ ์ •์˜ํ•ด์„œ ํ—ˆ์šฉํ•  ์ถœ์ฒ˜์™€ ๋ฉ”์„œ๋“œ๋ฅผ ์ง€์ •ํ•ด์„œ cors()์— ์ ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.



    11์žฅ. ์‹ค์ „: ์ฑ…์ž„์˜ ๋ถ„๋ฆฌ

    ์ธ์ฆ๋…ผ๋ฆฌ
    OTP ํ† ํฐ ์ธ์ฆ์„ ์–ด๋–ป๊ฒŒ ์ฒ˜๋ฆฌํ•  ๊ฒƒ์ธ๊ฐ€์— ๋Œ€ํ•ด ๊ทธ ์ˆœ์„œ๋ฅผ ์ฑ…์—์„œ ์ œ์‹œํ•œ ์ˆœ์„œ๋Œ€๋กœ ํ‘œ๊ธฐํ•˜๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

    1. ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์—”๋“œํฌ์ธํŠธ์™€ ํ•จ๊ป˜ ์ž๊ฒฉ์ฆ๋ช…์„ ๋ณด๋ƒ…๋‹ˆ๋‹ค.
    2. ๊ทธ๋Ÿผ ๋น„์ฆˆ๋‹ˆ์Šค ๋…ผ๋ฆฌ ์„œ๋ฒ„๋Š” ์ธ์ฆ์„œ๋ฒ„์—์„œ ์‚ฌ์šฉ์ž๋ฅผ ์ธ์ฆํ•˜๊ณ  OTP๋ฅผ ๋ณด๋ƒ…๋‹ˆ๋‹ค.
    3. ์—ฌ๊ธฐ์„œ ์ธ์ฆ์„œ๋ฒ„๋Š” ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์—์„œ ์‚ฌ์šฉ์ž๋ฅผ ์ธ์ฆํ•ฉ๋‹ˆ๋‹ค.
    4. ๊ทธ๋ฆฌ๊ณ  OTP๋ฅผ ํด๋ผ์ด์–ธํŠธ์— ๋ณด๋ƒ…๋‹ˆ๋‹ค.

    ๋น„์ง€๋‹ˆ์Šค ๋…ผ๋ฆฌ์„œ๋ฒ„์™€ ์ธ์ฆ์„œ๋ฒ„๊ฐ€ ์„œ๋กœ OTP๋ฅผ ์ฃผ๊ณ  ๋ฐ›๋Š” ๊ฑด ์ž˜๋ชป ๋๋‹ค๊ณ  ํ•  ์ˆ˜ ์žˆ์ง€๋งŒ ์ฑ…์—์„œ๋Š” ์„ค๋ช…์„ ์œ„ํ•ด ์‰ฌ์šด ๊ตฌํ˜„ ์˜ˆ์‹œ๋ฅผ ๋“ค์–ด ์„ค๋ช…ํ•˜๊ธฐ ์œ„ํ•ด ์ด๋ ‡๊ฒŒ ์„ค์ •ํ–ˆ๋‹ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค.

    ํ† ํฐ
    ์—”๋“œํฌ์ธํŠธ ์ ‘๊ทผ์„ ์œ„ํ•ด ํ—ค๋”์— ํ† ํฐ(UUID ํ˜น์€ JWT ๋“ฑ)์ด๋ผ๋Š” ๋ฌธ์ž์—ด์„ ๋„ฃ์–ด์„œ ์ธ์ฆํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋ฆฌ์†Œ์Šค์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

    • ์š”์ฒญ์‹œ๋งˆ๋‹ค ์ž๊ฒฉ ์ฆ๋ช… ๊ณต์œ ํ•  ํ•„์š”๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.
    • ์ˆ˜๋ช…์„ ์ง€์ •ํ•ด ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
    • ํ† ํฐ์— ์„ธ๋ถ€์ •๋ณด๋ฅผ ์ €์žฅํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

    JWT(JSON Web Token)
    JSON ๋ฐ์ดํ„ฐ ํ˜•์‹์„ ํฌํ•จํ•˜๋Š” ํ† ํฐ์„ ๋งํ•ฉ๋‹ˆ๋‹ค. ๋งˆ์นจํ‘œ๋กœ ๋ถ„๋ฆฌ๋œ ํ—ค๋”, ๋ณธ๋ฌธ, ์„œ๋ช…์˜ ์„ธ ๊ฐœ Base64 ์ธ์ฝ”๋”ฉ์œผ๋กœ ๊ตฌ์„ฑ๋ฉ๋‹ˆ๋‹ค. ์ž๋ฐ”์—์„œ๋Š” JJWT ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์ง€์›ํ•ด JWT ํ† ํฐ ์ƒ์„ฑ์„ ์‰ฝ๊ฒŒ ํ•  ์ˆ˜ ์žˆ๋„๋ก ์ง€์›ํ•ฉ๋‹ˆ๋‹ค. JJWT github overview์—์„œ ์‚ฌ์šฉ๋ฒ•, builder() ์ฒ˜๋ฆฌ, claims, parser(), key ์ฒ˜๋ฆฌ ๋ฐฉ๋ฒ•์— ๋Œ€ํ•ด ์•Œ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.



    12์žฅ. OAuth 2๊ฐ€ ์ž‘๋™ํ•˜๋Š” ๋ฐฉ๋ฒ•

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

    OAuth 2๋Š” ๋ฆฌ์†Œ์Šค ์„œ๋ฒ„, ์‚ฌ์šฉ์ž, ํด๋ผ์ด์–ธํŠธ, ๊ถŒํ•œ ๋ถ€์—ฌ ์„œ๋ฒ„๋กœ ๊ตฌ์„ฑ๋ฉ๋‹ˆ๋‹ค. ๋ฆฌ์†Œ์Šค ์†Œ์œ ์ž๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— ์š”์ฒญ์„ ๋ณด๋‚ด๊ณ  ๊ถŒํ•œ ๋ถ€์—ฌ ์„œ๋ฒ„์—์„œ ๋ฐ์ดํ„ฐ ์ž‘์—… ์Šน์ธ์„ ๋ฐ›์•˜๋‹ค๋Š” ์ฆ๋ช…์„ ์ œ๊ณต ๋ฐ›์Šต๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์ด ์ฆ๋ช…์„ ๋ฆฌ์†Œ์Šค ์„œ๋ฒ„์— ๋„˜๊ฒจ ํด๋ผ์ด์–ธํŠธ๊ฐ€ ๋ฐ์ดํ„ฐ๋ฅผ ์ด์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.
    ๊ถŒํ•œ ๋ถ€์—ฌ ์„œ๋ฒ„๋Š” ํด๋ผ์ด์–ธํŠธ์— ์•ก์„ธ์Šค ํ† ํฐ์„ ์ œ๊ณตํ•  ๋•Œ ๋ฆฌ๋””๋ ‰์…˜ URI๋กœ ํด๋ผ์ด์–ธํŠธ๋ฅผ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค.

    ์‚ฌ์šฉ์ž - ํด๋ผ์ด์–ธํŠธ - ๊ถŒํ•œ ๋ถ€์—ฌ ์„œ๋ฒ„ - ๋ฆฌ์†Œ์Šค ์„œ๋ฒ„
    

    OAuth 2 ๊ทธ๋žœํŠธ ์œ ํ˜•

    OAuth 2์—์„œ ๊ทธ๋žœํŠธ ์œ ํ˜•์€ ๊ฐ€์žฅ ๋งŽ์ด ์‚ฌ์šฉ๋˜๋Š” ์œ ํ˜•์œผ๋กœ ํ๋ฆ„์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

    ์Šน์ธ ์ฝ”๋“œ ๊ทธ๋žœํŠธ ์œ ํ˜•

    • ์Šน์ธ ์ฝ”๋“œ ๊ทธ๋žœํŠธ ์œ ํ˜•์œผ๋กœ ์ธ์ฆ ์š”์ฒญ ์ˆ˜ํ–‰
      • ์‚ฌ์šฉ์ž๋Š” ์—”๋“œํฌ์ธํŠธ๋ฅผ response_type, client_id, redirect_uri, scope, state๊ฐ€ ๋‹ด๊ธด ์ฟผ๋ฆฌ๋กœ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค.
      • ์ธ์ฆ ์„ฑ๊ณต์‹œ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ URI๋กœ ํด๋ผ์ด์–ธํŠธ๋ฅผ ํ˜ธ์ถœํ•˜๊ณ  ์ฝ”๋“œ์™€ ์ƒํƒœ๊ฐ’์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.
    • ์Šน์ธ ์ฝ”๋“œ ๊ทธ๋žœํŠธ ์œ ํ˜•์œผ๋กœ ์•ก์„ธ์Šค ํ† ํฐ ์–ป๊ธฐ
      • ์Šน์ธ ์ฝ”๋“œ๋ฅผ ๊ฐ€์ง€๊ณ  ์ธ์ฆ ์„œ๋ฒ„์—์„œ ํ† ํฌ๋ฅผ ๋ฐ›์Šต๋‹ˆ๋‹ค.
      • ์ด ๋•Œ ์š”์ฒญ์—๋Š” code, client_id, client_secret, redict_uri, grant_type๊ณผ ๊ฐ™์€ ์„ธ๋ถ€์ •๋ณด๊ฐ€ ๋“ค์–ด์žˆ์Šต๋‹ˆ๋‹ค.
    • ์Šน์ธ ์ฝ”๋“œ ๊ทธ๋žœํŠธ ์œ ํ˜•์œผ๋กœ ๋ณดํ˜ธ๋œ ๋ฆฌ์†Œ์Šค ํ˜ธ์ถœ

    ์•”ํ˜ธ ๊ทธ๋žœํŠธ ์œ ํ˜•
    ์Šน์ธ ์ฝ”๋“œ ๊ทธ๋žœํŠธ ์œ ํ˜•๊ณผ ๋‹ฌ๋ฆฌ ํด๋ผ์ด์–ธํŠธ์™€ ๊ถŒํ•œ ๋ถ€์—ฌ ์„œ๋ฒ„๋ฅผ ๊ฐ™์€ ์กฐ์ง์—์„œ ๊ตฌ์ถ•ํ•œ ๊ฒฝ์šฐ์— ์ด์šฉํ•ฉ๋‹ˆ๋‹ค. ๊ฑฐ์˜ ์œ ์‚ฌํ•˜์ง€๋งŒ ์Šน์ธ ์ฝ”๋“œ๋ฅผ ๋ฐ›๋Š” ๊ณผ์ • ์—†์ด ํด๋ผ์ด์–ธํŠธ๋Š” grant_type, client_id, client_secret, scope, username, password๊ฐ€ ๋‹ด๊ธด ์„ธ๋ถ€์ •๋ณด๋ฅผ ๋ณด๋‚ด๊ณ  ์•ก์„ธ์Šค ํ† ํฐ์„ ๋ฐ›์Šต๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์ด ํ† ํฐ์œผ๋กœ ๋ฆฌ์†Œ์Šค๋ฅผ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค. ์ฆ‰, ๋ฆฌ์†Œ์Šค ์†Œ์œ ์ž๊ฐ€ ํด๋ผ์ด์–ธํŠธ๋ฅผ ์‹ ๋ขฐํ•  ์ˆ˜ ์žˆ๋Š” ํ™˜๊ฒฝ์—์„œ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.

    ํด๋ผ์ด์–ธํŠธ ์ž๊ฒฉ ์ฆ๋ช… ๊ทธ๋žœํŠธ ์œ ํ˜•
    ์—ฌ๊ธฐ์„œ๋Š” ์‚ฌ์šฉ์ž๊ฐ€ ๊ด€์—ฌํ•˜์ง€ ์•Š๊ณ , ๋‘ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๊ฐ„ ์ธ์ฆ์„ ๊ตฌํ˜„ํ•˜๋Š” ๋ฐฉ์‹์„ ๋งํ•ฉ๋‹ˆ๋‹ค. grant_type, client_id, client_secret, scope๋ฅผ ๊ฐ€์ง€๊ณ  ๊ถŒํ•œ ๋ถ€์—ฌ ์„œ๋ฒ„์— ์š”์ฒญ์„ ๋ณด๋‚ธ ๋’ค, ์•ก์„ธ์Šค ํ† ํฐ์„ ๋ฐ›์•„ ๋ฆฌ์†Œ์Šค ์„œ๋ฒ„์— ํ˜ธ์ถœํ•˜๋Š” ๋ฐฉ์‹์ž…๋‹ˆ๋‹ค.

    ๊ฐฑ์‹ ํ† ํฐ์„ ์ด์šฉํ•˜๊ธฐ
    ์•”ํ˜ธ ๊ทธ๋žœํŠธ ์œ ํ˜•์€ ์‚ฌ์šฉ์ž์—๊ฒŒ ์žฌ์ธ์ฆ ์š”์ฒญ์„ ํ•˜๊ฑฐ๋‚˜ ์ž๊ฒฉ์ฆ๋ช…์„ ์ €์žฅํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๋ฐ˜๋ฉด ๊ฐฑ์‹  ํ† ํฐ์ด ์žˆ๋‹ค๋ฉด, ๋ณด์•ˆ ์œ„ํ—˜์ด ์ค„์–ด๋“ค๊ณ  ๊ถŒํ•œ ๋ถ€์—ฌ ์„œ๋ฒ„์—์„œ ์—‘์„ธ์Šค ํ† ํฐ๊ณผ ํ•จ๊ป˜ ๊ฐ™์ด ๋ฐ˜ํ™˜๋ฉ๋‹ˆ๋‹ค.
    ํด๋ผ์ด์–ธํŠธ ์ž๊ฒฉ ์ฆ๋ช… ๊ทธ๋žœํŠธ ์œ ํ˜•์—์„œ๋Š” ๋ถˆํ•„์š”ํ•œ๋ฐ, ์‚ฌ์šฉ์ž ์ž๊ฒฉ์ฆ๋ช…์ด ํ•„์š”ํ•˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.

    OAuth 2๋Š” ํ”„๋ ˆ์ž„์›Œํฌ์ด๋ฏ€๋กœ ๊ธฐ๋Šฅ์„ ์•Œ๊ณ  ์ œ๋Œ€๋กœ ๊ตฌํ˜„ํ•ด์•ผ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ทจ์•ฝ์„ฑ์„ ์™„ํ™”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

    ๊ฐ„๋‹จํ•œ SSO(Single Sign-On) ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๊ตฌํ˜„
    ๊ถŒํ•œ๋ถ€์—ฌ ์„œ๋ฒ„๋ฅผ ๊นƒํ—ˆ๋ธŒ๋ฅผ ์ด์šฉํ•ด ์Šน์ธ ์ฝ”๋“œ ๊ทธ๋žœํŠธ ์œ ํ˜•์„ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

    ๋จผ์ € ๊นƒํ—™ OAuth application์„ ๋“ฑ๋กํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

    ์ข…์†์„ฑ ์ถ”๊ฐ€

    • spring-boot-starter-oauth2-client
    • spring-boot-starter-security
    • spring-boot-starter-web

    Security Config ์„ค์ •์—๋Š” http.oauth2Login()์„ ์‚ฌ์šฉํ•ด ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. formLogin()์ฒ˜๋Ÿผ ํ•„ํ„ฐ์ฒด์ธ์— OAuth2LoginAuthenticationFilter ์ƒˆ ์ธ์ฆ ํ•„ํ„ฐ๋ฅผ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.

    ClientRegistration ์ธ์Šคํ„ด์Šค

    ClientRegistration cr = 
    	ClientRegistration.withRegistrationId("github")
    		.clientId("ae4a6f5a46fe6af5vs")
    		.clientSecret("1f6sadf86a5wef364f51dcs8ae4615")
    		.scope(new String[]("read:user"))
    		.authorizationUri("https://github.com/login/oauth/authorize")
    		.tokenUri("https://github.com/login/oauth/access_token")
    		.userInfoUri("https://api.github.com/user")
    		.userNameAttributeName("id")
    		.clientName("Github")
    		.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
    		.redirectUriTemplate("{baseUrl}/{action}/oauth2/code/{registrationId}")
    		.build();
    

    ์ด๋ฏธ ์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ์—์„œ๋Š” withRegistrationId()๋ฅผ ์ด์šฉํ•œ ์„ค์ •์„ ํ•˜์ง€ ์•Š์•„๋„ ์„ค์ •ํ•  ์ˆ˜ ์žˆ๋„๋ก ์ผ๋ฐ˜์ ์ธ ๊ณต๊ธ‰์ž์— ๋Œ€ํ•œ ์ธ์ฆ์„ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ClientOAuth2Provider๋กœ ์ง€์›ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

    ClientRegistration cr = 
    	ClientOAuth2Provider.GITHUB
    		.getBuilder("github")
    			.clientId("a4f6a5e3ws48d615a")
    			.clientSecret("a1465eafw184651waf1aw4615waw")
    			.build();
    

    ์ž‘์„ฑ์‹œ์  ์™œ ์•„์ง spring security doc์—์„œ ์•„์ง spring boot 2.x๋กœ ์„ค๋ช…ํ•˜๊ณ  ์žˆ๋Š”์ง€ ๋ชจ๋ฅด๊ฒ ์ง€๋งŒโ€ฆ.?! ์ด ์ฑ…์„ ์™„๋…ํ•˜๊ณ  spring boot 3.x ๊ธฐ์ค€ oauth 2 ์„ค์ • ์ƒ˜ํ”Œ ์ฝ”๋“œ์™€ ๋งํฌ๋ฅผ ์—…๋ฐ์ดํŠธํ•ด์•ผ ๊ฒ ๋‹ค๋Š” ์ƒ๊ฐ์ด ๋“ค์—ˆ๊ธฐ์— ์šฐ์„  ์ฑ… ๊ธฐ์ค€์œผ๋กœ ์„ค๋ช…ํ•˜๊ณ  ๋„˜์–ด๊ฐ€๊ฒ ์Šต๋‹ˆ๋‹ค;;

    ClientRegistrationRepositroy ๊ตฌํ˜„
    UserDetailsService์™€ ์œ ์‚ฌํ•˜๋ฉฐ, spring security์—์„œ๋Š” ClientRegistrationRepositroy์˜ ๊ตฌํ˜„์ธ InMemoryClientRegistrationRepository๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ๋นˆ์œผ๋กœ ๋“ฑ๋กํ•˜๊ฑฐ๋‚˜ oauth2login() ๋ฉ”์„œ๋“œ์˜ ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ Customizer ๊ฐ์ฒด๋ฅผ ์ด์šฉํ•ด์„œ ๋“ฑ๋กํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

    ์ „์ฒด SSO ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๊ตฌํ˜„์„ ์œ„ํ•œ ์ˆœ์„œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

    1. OAuth application ๋“ฑ๋กํ•˜๊ณ , client_id, client_secret ๋ฐ›์•„์˜ค๊ธฐ
    2. ์ข…์†์„ฑ ์ถ”๊ฐ€
    3. Security Configuration์œผ๋กœ OAuth 2๋ฅผ ์œ„ํ•œ ํ•„ํ„ฐ ์ ์šฉ
    4. ClientRegistration ์ธ์Šคํ„ด์Šค ๊ตฌํ˜„
    5. ClientRegistrationRepositroy ๊ตฌํ˜„
    6. OAuth2AuthenticationToken์—์„œ ์ธ์ฆ ์‚ฌ์šฉ์ž์˜ ์„ธ๋ถ€ ์ •๋ณด ์–ป์–ด์˜ค๊ธฐ



    13์žฅ. OAuth 2: ๊ถŒํ•œ ๋ถ€์—ฌ ์„œ๋ฒ„ ๊ตฌํ˜„

    ์•ž์„œ ๋ฐฐ์šด OAuth 2 ๊ทธ๋žœํŠธ ์œ ํ˜•์— ๋”ฐ๋ฅธ ๊ถŒํ•œ ๋ถ€์—ฌ ์„œ๋ฒ„๋ฅผ ๊ตฌํ˜„ํ•ฉ๋‹ˆ๋‹ค. ์ผ๋ฐ˜์ ์œผ๋กœ ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ๊นƒํ—™, ๊ตฌ๊ธ€ ๋“ฑ ๊ถŒํ•œ๋ถ€์—ฌ ์„œ๋ฒ„๋ฅผ ์ด์šฉํ•˜๋Š” ๊ฒฝ์šฐ๋Š” ํ•ด๋‹นํ•˜์ง€ ์•Š์ง€๋งŒ ์ง์ ‘ ๊ถŒํ•œ ๋ถ€์—ฌ ์„œ๋ฒ„๋ฅผ ๊ตฌํ˜„ํ•˜๋Š” ๊ฒฝ์šฐ์—๋Š” ๋งž์ถคํ˜•์œผ๋กœ ๊ถŒํ•œ ๋ถ€์—ฌ ์„œ๋ฒ„ ๊ตฌํ˜„์ด ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.

    • ์Šน์ธ์ฝ”๋“œ ๊ทธ๋žœํŠธ ์œ ํ˜•
    • ์•”ํ˜ธ ๊ทธ๋žœํŠธ ์œ ํ˜•
    • ํด๋ผ์ด์–ธํŠธ ์ž๊ฒฉ ์ฆ๋ช… ๊ทธ๋žœํŠธ ์œ ํ˜•

    ํ˜„์žฌ Spring security๋Š” OAuth 2์™€ ๊ด€๋ จํ•ด์„œ ๋กœ๊ทธ์ธ, ํด๋ผ์ด์–ธํŠธ, ๋ฆฌ์†Œ์Šค ์„œ๋ฒ„์™€ ๊ด€๋ จ๋œ ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ๊ด€๋ จ Link

    ์ฑ…์—์„œ๋Š” spring security oauth 2์˜ ์ข…์†์„ฑ ์ง€์›์ด ์ค‘๋‹จ๋œ ์‹œ์ ์œผ๋กœ๋ถ€ํ„ฐ ๋งž์ถคํ˜• ๊ถŒํ•œ ๋ถ€์—ฌ ์„œ๋ฒ„๋ฅผ ๊ตฌํ˜„ํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ๋‹ค๋ฃจ๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.
    (Keycloak, Okta์™€ ๊ฐ™์€ ํˆด์„ ๋Œ€์•ˆ์œผ๋กœ ์„ ํƒํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.)

    ๋งŸ์ถคํ˜• ๊ถŒํ•œ ๋ถ€์—ฌ ์„œ๋ฒ„ ๊ตฌํ˜„
    @EnableAuthorizationServer๋ฅผ ์‚ฌ์šฉํ•ด configuration์„ ๊ถŒํ•œ ๋ถ€์—ฌ ์„œ๋ฒ„ ๊ตฌ์„ฑ์„ ์œ„ํ•œ ์ค€๋น„๋ฅผ ํ•ฉ๋‹ˆ๋‹ค.
    ๊ถŒํ•œ ๋ถ€์—ฌ ์„œ๋ฒ„๋„ ์ž์ฒด ์ž๊ฒฉ์ฆ๋ช…์ด ํ•„์š”ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์‚ฌ์šฉ์ž์™€ ํด๋ผ์ด์–ธํŠธ์— ๋Œ€ํ•œ ์ž๊ฒฉ์ฆ๋ช…์„ ์ €์žฅํ•˜๊ณ  ํด๋ผ์ด์–ธํŠธ ์ž๊ฒฉ์ฆ๋ช…์ด ๋“ฑ๋ก๋œ ๊ฒฝ์šฐ์— ํ•œํ•ด์„œ ๊ถŒํ•œ์„ ๋ถ€์—ฌํ•ฉ๋‹ˆ๋‹ค.

    ๋ฉ”๋ชจ๋ฆฌ์—์„œ ClientDetails๋ฅผ ๊ตฌ์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Š” UserDetailsService๋ฅผ ์ด์šฉํ•  ๋•Œ์™€ ์œ ์‚ฌํ•œ ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•˜๋Š”๋ฐ ์‹ค์ œ๋กœ๋Š” ๋ฉ”๋ชจ๋ฆฌ๊ฐ€ ์•„๋‹Œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ์ด์šฉํ•ด ๊ตฌํ˜„ํ•˜๋Š” ๊ฒƒ์ด ๋ฐ”๋žŒ์งํ•ฉ๋‹ˆ๋‹ค.

    clients.inMemory()
    	.withClient("client")
    	.secret("secret")
    	.authorizedGrantTypes("password")
    	.scope("read");
    

    ์Šน์ธ ์ฝ”๋“œ ๊ทธ๋žœํŠธ ์œ ํ˜•์ด ๊ฐ€๋Šฅํ•˜๊ฒŒ ํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ํด๋ผ์ด์–ธํŠธ์— ์ œ๊ณต๋œ ์Šน์ธ ์ฝ”๋“œ๋ฅผ ์ด์šฉํ•ด์„œ ์•ก์„ธ์Šค ํ† ํฐ์„ ์–ป์„ ์ˆ˜ ์žˆ๋„๋ก ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ฆ‰ ๊ถŒํ•œ ๋ถ€์—ฌ ์„œ๋ฒ„๋Š” ํด๋ผ์ด์–ธํŠธ์— ๋ฆฌ๋””๋ ‰์…˜ URI์™€ ์Šน์ธ์ฝ”๋“œ๋ฅผ ๋ฐ˜ํ™˜ํ•  ์ˆ˜ ์žˆ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

    ํด๋ผ์ด์–ธํŠธ๋Š” ๋ชจ๋‘ ๊ฐ™์€ ์ž๊ฒฉ์ฆ๋ช…์œผ๋กœ ๊ถŒํ•œ ๋ถ€์—ฌ ์„œ๋ฒ„์—์„œ ์—‘์„ธ์Šค ํ† ํฐ์„ ์–ป์„ ์ˆ˜ ์—†๋„๋ก ๊ตฌํ˜„ํ•˜๋Š” ๊ฒƒ์ด ๋ฐ”๋žŒ์งํ•ฉ๋‹ˆ๋‹ค. ๊ทธ ์ด์œ ๋Š” ํ•˜๋‚˜์˜ ํ† ํฐ์„ ์–ป๋Š” ๊ฒฝ์šฐ ๋‹ค๋ฅธ ํด๋ผ์ด์–ธํŠธ๋„ ๋ชจ๋‘ ํ•ดํ‚นํ•  ์ˆ˜ ์žˆ ์œ„ํ—˜์ด ์žˆ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.

    ๊ฐฑ์‹  ํ† ํฐ ์œ ํ˜•์„ ์ด์šฉํ•ด์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ์—๋Š” authorizedGrantTypes()์— refresh_token ์œ ํ˜•์„ ๋„ฃ์–ด์ฃผ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.



    14์žฅ. OAuth 2: ๋ฆฌ์†Œ์Šค ์„œ๋ฒ„ ๊ตฌํ˜„

    ์‚ฌ์šฉ์ž๊ฐ€ ๋ฆฌ์†Œ์Šค ์„œ๋ฒ„์— ์ ‘๊ทผํ•˜๋ ค๊ณ  ํ•  ๋•Œ, ๋ฆฌ์†Œ์Šค ์„œ๋ฒ„๋Š” ์‚ฌ์šฉ์ž๊ฐ€ ์ „๋‹ฌํ•œ ์•ก์„ธ์Šค ํ† ํฐ์ด ์˜ฌ๋ฐ”๋ฅธ์ง€ ํ™•์ธํ•˜๋Š” ๊ณผ์ •์„ ๊ฑฐ์นฉ๋‹ˆ๋‹ค. ์ด ๋•Œ ์‚ฌ์šฉ๋˜๋Š” ํ† ํฐ ๊ฒ€์ฆ ๋ฐฉ์‹์€ ๋‹ค์–‘ํ•œ๋ฐ, ๊ถŒํ•œ ๋ถ€์—ฌ ์„œ๋ฒ„์—์„œ ๊ฒ€ํ† ํ•˜๊ฒŒ ํ•˜๋Š” ๋ฐฉ์‹, ๋ฆฌ์†Œ์Šค ์„œ๋ฒ„์˜ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ฐธ์กฐ ๋ฐฉ์‹, ํ† ํฐ ์„œ๋ช…์„ ์ด์šฉํ•˜๋Š” ๋ฐฉ์‹์ด ์žˆ์Šต๋‹ˆ๋‹ค.

    ์ด ๋ถ€๋ถ„๋„ ํ˜„์žฌ spring security์™€ ์ฐจ์ด๊ฐ€ ์žˆ์–ด์„œ(@EnableResourceServer ์ง€์› ์ค‘๋‹จ ๋“ฑ) ํฐ ํ๋ฆ„๋งŒ ์ง‘๊ณ  ๋„˜์–ด๊ฐ€๊ธฐ๋กœ ํ–ˆ์Šต๋‹ˆ๋‹ค.

    ์›๊ฒฉ์œผ๋กœ ๊ฒ€์ฆ
    ๋ฆฌ์†Œ์Šค ์„œ๋ฒ„๊ฐ€ ์ง์ ‘ ๊ถŒํ•œ ๋ถ€์—ฌ ์„œ๋ฒ„๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ํ† ํฐ ๊ฒ€์ฆ์„ ๊ตฌํ˜„ํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ๋ฆฌ์†Œ์Šค ์„œ๋ฒ„๋Š” ํด๋ผ์ด์–ธํŠธ์—๊ฒŒ ๋ฐ›์€ ํ† ํฐ์„ ๊ถŒํ•œ ๋ถ€์—ฌ ์„œ๋ฒ„์— ์ „๋‹ฌํ•˜๊ณ  ์‚ฌ์šฉ์ž ์„ธ๋ถ€ ์ •๋ณด๋ฅผ ์–ป์Šต๋‹ˆ๋‹ค. ๋Œ€์‹  ์ƒˆ๋กœ์šด ํ† ํฐ์ด ์žˆ์„ ๋•Œ๋งˆ๋‹ค ๊ถŒํ•œ ๋ถ€์—ฌ ์„œ๋ฒ„๋ฅผ ํ•ญ์ƒ ํ˜ธ์ถœํ•ด์•ผ ํ•œ๋‹ค๋Š” ๋‹จ์ ์ด ์žˆ์Šต๋‹ˆ๋‹ค.

    ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ฐธ์กฐ๋ฐฉ์‹
    ์œ„ ๋ฐฉ์‹์ฒ˜๋Ÿผ ๋งค๋ฒˆ ๊ถŒํ•œ ๋ถ€์—ฌ ์„œ๋ฒ„์— ์ ‘๊ทผํ•  ํ•„์š”๋Š” ์‚ฌ๋ผ์กŒ์ง€๋งŒ, ๊ณต์œ  ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ์ถ”๊ฐ€ํ•ด์•ผ ํ•˜๊ณ  ๋ณ‘๋ชฉ์ด ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ๋‹จ์ ์ด ์žˆ์Šต๋‹ˆ๋‹ค.
    ์ธ์ฆ ํ•„ํ„ฐ์—์„œ HTTP ์š”์ฒญ์„ ๊ฐ€๋กœ์ฑ„๊ณ  ์ด๋ฅผ ํ† ํฐ ์ €์žฅ์†Œ(TokenStore)์—์„œ ํ† ํฐ ๊ฒ€์ฆ ํ›„ ์‚ฌ์šฉ์ž ์„ธ๋ถ€ ์ •๋ณด๋ฅผ ๊ฒ€์ƒ‰ํ•ฉ๋‹ˆ๋‹ค. ์ด ์„ธ๋ถ€ ์ •๋ณด๋Š” ๋ณด์•ˆ ์ปจํ…์ŠคํŠธ์— ์ €์žฅํ•ฉ๋‹ˆ๋‹ค.



    15์žฅ. OAuth 2: JWT์™€ ์•”ํ˜ธํ™” ์„œ๋ช… ์‚ฌ์šฉ


    results matching ""

      No results matching ""