๐ŸŽฏ 18์ฃผ์ฐจ ํ•™์Šต ์ปค๋ฆฌํ˜๋Ÿผ โ€” Spring Security ์™„์ „ ์ •๋ณต

Psjยท2026๋…„ 5์›” 6์ผ

F-lab

๋ชฉ๋ก ๋ณด๊ธฐ
19/52

๐ŸŽฏ 18์ฃผ์ฐจ ํ•™์Šต ์ปค๋ฆฌํ˜๋Ÿผ โ€” Spring Security ์™„์ „ ์ •๋ณต

17์ฃผ์ฐจ(๋ถ„์‚ฐ ์‹œ์Šคํ…œ) ์ดํ›„ Claude๊ฐ€ ์ž„์˜๋กœ ๊ตฌ์„ฑํ•œ ํ•™์Šต ๊ฒฝ๋กœ.
๋ฉด์ ‘ ๊ฑฐ์˜ 100% ์ถœ์ œ ์˜์—ญ์ธ Spring Security์™€ ์ธ์ฆ/์ธ๊ฐ€๋ฅผ ์ •๋ณตํ•œ๋‹ค.

  • Filter Chain ๋ฉ”์ปค๋‹ˆ์ฆ˜ (15์ฃผ์ฐจ Filter์˜ ์ง„์งœ ํ™œ์šฉ)
  • ์ธ์ฆ(Authentication) vs ์ธ๊ฐ€(Authorization)
  • Session ๊ธฐ๋ฐ˜ vs Token ๊ธฐ๋ฐ˜ (๋Œ€๊ทœ๋ชจ ์‹œ์Šคํ…œ์˜ ๊ฒฐ์ •)
  • OAuth2์™€ JWT (ํ˜„๋Œ€ ํ‘œ์ค€)

4๋…„์ฐจ ํ’€์Šคํƒ ๊ฐœ๋ฐœ์ž๊ฐ€ ๊ฐ€์žฅ ์ž์ฃผ ๋งˆ์ฃผํ•˜์ง€๋งŒ "์ œ๋Œ€๋กœ๋Š” ๋ชจ๋ฅด๋Š”" ์˜์—ญ.


๐Ÿค” ์™œ 18์ฃผ์ฐจ์— Spring Security์ธ๊ฐ€

1~17์ฃผ์ฐจ์˜ ๋นˆ ๊ณต๊ฐ„:

์˜์—ญ์ฃผ์ฐจ์ƒํƒœ
Java/Spring/JPA1-12์ฃผ์ฐจโœ…
DB13-14์ฃผ์ฐจโœ…
Spring MVC15์ฃผ์ฐจโœ…
๋ถ„์‚ฐ ์‹œ์Šคํ…œ16-17์ฃผ์ฐจโœ…
Spring Security + ์ธ์ฆ/์ธ๊ฐ€๋ฏธ๋“ฑ์žฅโŒ

์™œ ๊ฒฐ์ •์ ์ธ๊ฐ€:

  1. ๋ฉด์ ‘ ๊ฑฐ์˜ 100% ์ถœ์ œ โ€” "JWT ์ธ์ฆ์„ ๊ตฌํ˜„ํ•ด ๋ณด์…จ๋‚˜์š”?" "Session vs Token ์ฐจ์ด๋Š”?"
  2. 15์ฃผ์ฐจ Filter์˜ ์ง„์งœ ๋ฌด๋Œ€ โ€” Spring Security๊ฐ€ Filter ๊ธฐ๋ฐ˜์˜ ๊ฐ€์žฅ ๊ฐ•๋ ฅํ•œ ์‚ฌ๋ก€
  3. 17์ฃผ์ฐจ MSA์˜ ํ•„์ˆ˜ ์š”์†Œ โ€” ๋ถ„์‚ฐ ์‹œ์Šคํ…œ์—์„œ ์ธ์ฆ/์ธ๊ฐ€๋Š” ํ•ต์‹ฌ ๊ฒฐ์ •
  4. ์‹ค๋ฌด 95% ์‚ฌ์šฉ โ€” ๊ฑฐ์˜ ๋ชจ๋“  Spring Boot ์•ฑ์— Security ์‚ฌ์šฉ
  5. ์ทจ์•ฝ์  ์ง๊ฒฐ โ€” ๋ณด์•ˆ ์‚ฌ๊ณ ๋Š” ์ปค๋ฆฌ์–ด์™€ ํšŒ์‚ฌ์— ์น˜๋ช…์ 

ILIC ๊ด€์ :

  • ํ˜„์žฌ Spring Security ์‚ฌ์šฉ ์ถ”์ • (B2B SaaS๋Š” ๋ณด์•ˆ ํ•„์ˆ˜)
  • ๊ทธ๋Ÿฌ๋‚˜ 4๋…„์ฐจ ๊ฐœ๋ฐœ์ž๊ฐ€ "๋‚ด๋ถ€ ๋™์ž‘๊นŒ์ง€ ์ •ํ™•ํžˆ ์•„๋Š”๊ฐ€" ๋Š” ๋˜ ๋‹ค๋ฅธ ๋ฌธ์ œ

๐Ÿ“Š ํ•™์Šต ๊ฒฝ๋กœ ํ•œ๋ˆˆ์— ๋ณด๊ธฐ

[Phase 1] ์ธ์ฆ/์ธ๊ฐ€์˜ ๋ณธ์งˆ (Authentication vs Authorization)
   โ†“
[Phase 2] Spring Security ์•„ํ‚คํ…์ฒ˜์™€ Filter Chain โ—„ ์ •์  1
   โ†“
[Phase 3] ์ธ์ฆ ์ฒ˜๋ฆฌ ํ๋ฆ„ (UserDetails, AuthenticationProvider)
   โ†“
[Phase 4] ์ธ๊ฐ€ ์ฒ˜๋ฆฌ (URL ํŒจํ„ด, ๋ฉ”์„œ๋“œ ๋ณด์•ˆ, AOP)
   โ†“
[Phase 5] Session ๊ธฐ๋ฐ˜ vs Token ๊ธฐ๋ฐ˜ โ—„ ์ •์  2 (โ˜… ๋ฉด์ ‘ ๋‹จ๊ณจ)
   โ†“
[Phase 6] JWT ์™„์ „ ์ •๋ณต (๊ตฌ์กฐ, ๊ฒ€์ฆ, ๋ณด์•ˆ ์ทจ์•ฝ์ )
   โ†“
[Phase 7] OAuth2์™€ OpenID Connect
   โ†“
[Phase 8] ๋ณด์•ˆ ์ทจ์•ฝ์ ๊ณผ ๋ฐฉ์–ด (CSRF, XSS, CORS)

์ด 8 Phase ร— 27 Unit โ€” ๋ฉด์ ‘ ๋‹จ๊ณจ ์ •์  2๊ฐœ๋ฅผ ๊ฐ€์ง„ ๋‹จ์ผ ์ฃผ์ฐจ.

๐Ÿ”— 1~18์ฃผ์ฐจ ํ๋ฆ„ ์ •๋ฆฌ

์ฃผ์ฐจ์ฃผ์ œ์˜๋ฏธ
1~17์ฃผ์ฐจJava + Spring + JPA + DB + MVC + ๋ถ„์‚ฐ๊ธฐ๋Šฅ ๊ตฌํ˜„
18์ฃผ์ฐจ (์ง€๊ธˆ)Spring Security + ์ธ์ฆ/์ธ๊ฐ€๋ณด์•ˆ ์˜์—ญ

ํ•ต์‹ฌ ์—ฐ๊ฒฐ:

  • 15์ฃผ์ฐจ Filter โ†’ 18์ฃผ์ฐจ์—์„œ ๋ณธ๊ฒฉ ํ™œ์šฉ
  • 8-9์ฃผ์ฐจ AOP โ†’ 18์ฃผ์ฐจ ๋ฉ”์„œ๋“œ ๋ณด์•ˆ (@PreAuthorize)
  • 17์ฃผ์ฐจ ๋ถ„์‚ฐ ์‹œ์Šคํ…œ โ†’ 18์ฃผ์ฐจ JWT/OAuth2

๐Ÿ—“๏ธ ๊ถŒ์žฅ ํ•™์Šต ์ผ์ • (์••์ถ• 7์ผ)

DayPhaseํ•™์Šต ๋ชฉํ‘œ
1์ผ์ฐจPhase 1 + 2์ธ์ฆ/์ธ๊ฐ€ ๋ณธ์งˆ + Filter Chain (โ˜…)
2์ผ์ฐจPhase 3์ธ์ฆ ํ๋ฆ„ (UserDetails ๋“ฑ)
3์ผ์ฐจPhase 4์ธ๊ฐ€ (URL + ๋ฉ”์„œ๋“œ)
4์ผ์ฐจPhase 5Session vs Token (โ˜… ๋ฉด์ ‘ ๋‹จ๊ณจ)
5์ผ์ฐจPhase 6JWT ๊นŠ์ด
6์ผ์ฐจPhase 7OAuth2/OIDC
7์ผ์ฐจPhase 8 + ์ข…ํ•ฉCSRF/XSS/CORS + ์ž๊ธฐ ์ ๊ฒ€

์—ฌ์œ  ์ผ์ • (10์ผ): Phase 2, 5์— +1์ผ์”ฉ. ์ง์ ‘ ๋””๋ฒ„๊ฑฐ๋กœ Filter Chain step-through ๊ถŒ์žฅ.


๐Ÿ“š Phase 1 โ€” ์ธ์ฆ/์ธ๊ฐ€์˜ ๋ณธ์งˆ

๋ชฉํ‘œ: ๊ฐ€์žฅ ์ž์ฃผ ํ—ท๊ฐˆ๋ฆฌ๋Š” ๋‘ ๊ฐœ๋…์„ ์ •ํ™•ํžˆ ๋ถ„๋ฆฌํ•œ๋‹ค.

Unit 1.1 โ€” Authentication vs Authorization โญโญโญ

์„ ์ˆ˜ ์ง€์‹: ์—†์Œ (๊ฐ€์žฅ ๊ธฐ์ดˆ)

ํ•ต์‹ฌ ์ •์˜

Authentication (์ธ์ฆ) โ€” "๋ˆ„๊ตฌ์ธ๊ฐ€?":

"์ด ์š”์ฒญ์„ ๋ณด๋‚ธ ์‚ฌ๋žŒ์ด ๋ณธ์ธ์ด ๋งž๋Š”๊ฐ€" ๊ฒ€์ฆ

Authorization (์ธ๊ฐ€) โ€” "๋ฌด์—‡์„ ํ•  ์ˆ˜ ์žˆ๋Š”๊ฐ€?":

"์ธ์ฆ๋œ ์‚ฌ์šฉ์ž๊ฐ€ ์ด ์ž์›์— ์ ‘๊ทผ ๊ฐ€๋Šฅํ•œ๊ฐ€" ๊ฒ€์ฆ


๋น„์œ  โ€” ํšŒ์‚ฌ ์ถœ์ž…:

  • Authentication: ์ถœ์ž…์ฆ์œผ๋กœ ์‹ ์› ํ™•์ธ โ†’ "๋ฐ•์Šน์ œ ๋งž์œผ์‹œ๋„ค์š”"
  • Authorization: ๊ทธ ์‚ฌ๋žŒ์ด ์ด ์ธต/๋ฐฉ์— ๋“ค์–ด๊ฐˆ ์ˆ˜ ์žˆ๋Š”๊ฐ€ โ†’ "๋‹น์‹ ์€ ์ž„์›์‹ค ์ถœ์ž… ๊ถŒํ•œ์ด ์—†์Šต๋‹ˆ๋‹ค"

โ†’ ์ธ์ฆ OK์—ฌ๋„ ์ธ๊ฐ€๋Š” NO์ผ ์ˆ˜ ์žˆ์Œ


ํ๋ฆ„:

[Request]
   โ†“
[Authentication]
   - ๋ˆ„๊ตฌ์ธ๊ฐ€? (ID/PW, JWT, ์ธ์ฆ์„œ ๋“ฑ)
   - ์‹คํŒจ โ†’ 401 Unauthorized
   โ†“ (ํ†ต๊ณผ)
[Authorization]
   - ๊ถŒํ•œ์ด ์žˆ๋Š”๊ฐ€?
   - ์‹คํŒจ โ†’ 403 Forbidden
   โ†“ (ํ†ต๊ณผ)
[Business Logic]

HTTP ์ƒํƒœ ์ฝ”๋“œ (15์ฃผ์ฐจ ๋ณต์Šต) โญ :

  • 401 Unauthorized: ์ธ์ฆ ์‹คํŒจ (๋‹น์‹ ์ด ๋ˆ„๊ตฐ์ง€ ๋ชจ๋ฆ„)
  • 403 Forbidden: ์ธ๊ฐ€ ์‹คํŒจ (๋‹น์‹ ์ด ๋ˆ„๊ตฐ์ง€ ์•Œ์ง€๋งŒ ๊ถŒํ•œ ์—†์Œ)

401๊ณผ 403์˜ ์ฐจ์ด๋Š” ๋ฉด์ ‘ ๋‹จ๊ณจ์ž…๋‹ˆ๋‹ค.


ILIC ์‹œ๋‚˜๋ฆฌ์˜ค:

Authentication:

  • ๋กœ๊ทธ์ธ โ†’ ID/PW ๊ฒ€์ฆ โ†’ ์‚ฌ์šฉ์ž ์‹๋ณ„
  • ๋˜๋Š” JWT ํ† ํฐ ๊ฒ€์ฆ

Authorization:

  • ์ผ๋ฐ˜ ์‚ฌ์šฉ์ž โ†’ ์ž๊ธฐ ์šด์ž„ ๊ฒฌ์ ๋งŒ
  • ๊ด€๋ฆฌ์ž โ†’ ๋ชจ๋“  ์šด์ž„ ๊ฒฌ์ 
  • ์™ธ๋ถ€ ํŒŒํŠธ๋„ˆ โ†’ ํŠน์ • ๊ฒฌ์ ๋งŒ

์ž๊ธฐ ์ ๊ฒ€

  • "์ธ์ฆ ์—†์ด ์ธ๊ฐ€๊ฐ€ ๊ฐ€๋Šฅํ•œ๊ฐ€?" (ํžŒํŠธ: ์‚ฌ์‹ค์ƒ NO โ€” ์ต๋ช… ์ ‘๊ทผ์€ ๋ณ„๊ฐœ)
  • "์ธ์ฆ์€ ๋๋Š”๋ฐ ์ธ๊ฐ€๊ฐ€ ์•ˆ ๋œ ์‚ฌ์šฉ์ž์—๊ฒŒ 401์„ ์ฃผ๋ฉด?" (ํžŒํŠธ: ์ •๋ณด ๋…ธ์ถœ โ€” ๋ฐ˜๋“œ์‹œ 403)

Unit 1.2 โ€” ์ธ์ฆ ๋ฐฉ์‹์˜ ์ง„ํ™”

์„ ์ˆ˜ ์ง€์‹: Unit 1.1

์—ญ์‚ฌ์  ์ง„ํ™” โญ :

1. Basic Authentication (๊ฐ€์žฅ ๋‹จ์ˆœ)

Authorization: Basic dXNlcjpwYXNzd29yZA==  (Base64)

๋ฌธ์ œ:

  • Base64๋Š” ์ธ์ฝ”๋”ฉ (์•”ํ˜ธํ™” X) โ†’ ํ‰๋ฌธ ๋…ธ์ถœ
  • ๋งค ์š”์ฒญ๋งˆ๋‹ค ID/PW ์ „์†ก
  • HTTPS ์—†์ด๋Š” ์œ„ํ—˜

ํ˜„์žฌ:

  • ๋‚ด๋ถ€ API, ๋‹จ์ˆœ ์‹œ์Šคํ…œ์—์„œ๋งŒ ์‚ฌ์šฉ

[Login]
   โ†“ ID/PW ๊ฒ€์ฆ
[Server]
   โ†“ Session ์ƒ์„ฑ + Session ID
[Browser] โ† Cookie: SESSIONID=abc123
   โ†“ ์ดํ›„ ์š”์ฒญ๋งˆ๋‹ค Cookie ์ž๋™ ์ „์†ก
[Server] โ†’ Session ID๋กœ ์‚ฌ์šฉ์ž ์‹๋ณ„

์žฅ์ : ๋‹จ์ˆœ, Spring ๊ธฐ๋ณธ
๋‹จ์ : ์„œ๋ฒ„ ์ƒํƒœ ๋ณด์œ  โ€” ๋ถ„์‚ฐ ํ™˜๊ฒฝ์—์„œ ์–ด๋ ค์›€ (Phase 5์—์„œ)


3. Token ๊ธฐ๋ฐ˜ (JWT ๋“ฑ)

[Login]
   โ†“ ID/PW ๊ฒ€์ฆ
[Server]
   โ†“ JWT ์ƒ์„ฑ (์„œ๋ช…)
[Browser] โ† JWT ์ €์žฅ
   โ†“ Authorization: Bearer eyJ...
[Server] โ†’ JWT ๊ฒ€์ฆ (์„œ๋ช…๋งŒ ํ™•์ธ, ์ƒํƒœ ์—†์Œ)

์žฅ์ : Stateless, ๋ถ„์‚ฐ ํ™˜๊ฒฝ ์ ํ•ฉ
๋‹จ์ : ํ† ํฐ ํ๊ธฐ ์–ด๋ ค์›€, ํฌ๊ธฐ โ†‘


4. OAuth2 / OIDC (ํ˜„๋Œ€ ํ‘œ์ค€)

[Client] โ†’ "Google๋กœ ๋กœ๊ทธ์ธ"
   โ†“
[Google] (Authorization Server) โ†’ ์‚ฌ์šฉ์ž ๋™์˜
   โ†“
[Client] โ† Access Token + Refresh Token
   โ†“
[Resource Server (API)] โ† Access Token์œผ๋กœ ์ธ์ฆ

์žฅ์ :

  • ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์ž์ฒด ์ €์žฅ X
  • ๊ถŒํ•œ ์œ„์ž„ ํ‘œ์ค€
  • ๋‹ค์–‘ํ•œ Provider ํ†ตํ•ฉ

ํ˜„์žฌ ํ‘œ์ค€:

  • ๋ชจ๋†€๋ฆฌ์‹ + B2C ์›น โ†’ Session-Cookie๋„ OK
  • SPA + REST API โ†’ JWT
  • ๋ชจ๋ฐ”์ผ ์•ฑ / ์„œ๋“œํŒŒํ‹ฐ ํ†ตํ•ฉ โ†’ OAuth2
  • MSA โ†’ JWT (Stateless ํ•ต์‹ฌ)

ILIC ์ถ”์ •:

  • B2B SaaS + Vue 3 SPA โ†’ JWT ๊ฐ€๋Šฅ์„ฑ โ†‘
  • ๋˜๋Š” Session-Cookie๋กœ ์‹œ์ž‘ ํ›„ ์ ์ง„ ์ „ํ™˜

์ž๊ธฐ ์ ๊ฒ€

  • 4๊ฐ€์ง€ ๋ฐฉ์‹์˜ ๊ฒฐ์ •์  ์ฐจ์ด๋Š”?
  • ILIC๊ฐ€ ์–ด๋–ค ๋ฐฉ์‹์„ ์“ฐ๊ณ  ์žˆ๊ณ , ์™œ ๊ทธ ์„ ํƒ์„ ํ–ˆ๋Š”๊ฐ€?

๐Ÿ“š Phase 2 โ€” Spring Security ์•„ํ‚คํ…์ฒ˜์™€ Filter Chain (โ˜… ์ •์  1)

๋ชฉํ‘œ: ๋ฉด์ ‘ ๋‹จ๊ณจ โ€” Spring Security๊ฐ€ ์–ด๋–ป๊ฒŒ ๋™์ž‘ํ•˜๋Š”์ง€ Filter Chain์„ ํ†ตํ•ด ์ดํ•ดํ•œ๋‹ค.

Unit 2.1 โ€” Spring Security ์œ„์น˜์™€ ๋™์ž‘ ์›๋ฆฌ

์„ ์ˆ˜ ์ง€์‹: 15์ฃผ์ฐจ Phase 5 (Filter)

ํ•ต์‹ฌ ๊ทธ๋ฆผ โญ :

[Client]
   โ†“ HTTP Request
[Servlet Container (Tomcat)]
   โ†“
[Filter Chain] โ”€โ”€โ”€โ”€โ”€โ”€โ”€ โ˜… Spring Security๊ฐ€ ์—ฌ๊ธฐ โ˜…
   โ”œโ”€โ”€ DelegatingFilterProxy
   โ”‚   โ””โ”€โ”€ FilterChainProxy (Spring Security)
   โ”‚       โ”œโ”€โ”€ SecurityContextPersistenceFilter
   โ”‚       โ”œโ”€โ”€ UsernamePasswordAuthenticationFilter
   โ”‚       โ”œโ”€โ”€ BasicAuthenticationFilter
   โ”‚       โ”œโ”€โ”€ ExceptionTranslationFilter
   โ”‚       โ””โ”€โ”€ FilterSecurityInterceptor (์ธ๊ฐ€)
   โ†“
[DispatcherServlet] (15์ฃผ์ฐจ)
   โ†“
[Controller]

ํ•ต์‹ฌ ํ†ต์ฐฐ:

"Spring Security๋Š” Servlet Filter ๋กœ ๋™์ž‘ โ†’ DispatcherServlet ๋„๋‹ฌ ์ „์— ์ฐจ๋‹จ/ํ†ต๊ณผ ๊ฒฐ์ •"

์™œ Filter์ธ๊ฐ€ (15์ฃผ์ฐจ ๋ณต์Šต):

  • DispatcherServlet ์™ธ๊ณฝ โ†’ ๊ฐ€์žฅ ๊ฐ•๋ ฅํ•œ ์ฐจ๋‹จ ์œ„์น˜
  • Spring ๋นˆ ์ •๋ณด ์—†์ด๋„ ๋™์ž‘
  • Servlet ํ‘œ์ค€์ด๋ผ ๋‹ค๋ฅธ ํ”„๋ ˆ์ž„์›Œํฌ์™€๋„ ํ˜ธํ™˜

DelegatingFilterProxy:

  • Servlet Filter (ํ‘œ์ค€)
  • ๊ทธ๋Ÿฌ๋‚˜ ์‹ค์ œ ์ฒ˜๋ฆฌ๋Š” Spring ๋นˆ์œผ๋กœ ์œ„์ž„
  • โ†’ Filter ์•ˆ์—์„œ Spring DI ์‚ฌ์šฉ ๊ฐ€๋Šฅ

FilterChainProxy:

  • Spring Security์˜ ์ง„์งœ ์‹œ์ž‘์ 
  • ์—ฌ๋Ÿฌ SecurityFilterChain์„ ๊ด€๋ฆฌ
  • ์š”์ฒญ URL์— ๋”ฐ๋ผ ์ ์ ˆํ•œ Filter ์ฒด์ธ ์„ ํƒ

์ž๊ธฐ ์ ๊ฒ€

  • ์™œ Spring Security Filter๋“ค์ด ์ผ๋ฐ˜ Servlet Filter๊ฐ€ ์•„๋‹Œ Spring ๋นˆ์ธ๊ฐ€? (ํžŒํŠธ: DI, AOP)
  • DispatcherServlet๊ณผ Spring Security์˜ ํ˜ธ์ถœ ์ˆœ์„œ๋Š”? (ํžŒํŠธ: Security๊ฐ€ ๋จผ์ €)

Unit 2.2 โ€” Security Filter Chain ํ•ต์‹ฌ Filter๋“ค โญโญ

์„ ์ˆ˜ ์ง€์‹: Unit 2.1

์ฃผ์š” Filter ์ˆœ์„œ (์‹ค์ œ๋กœ๋Š” ๋” ๋งŽ์ง€๋งŒ ํ•ต์‹ฌ๋งŒ):

1. SecurityContextPersistenceFilter
   โ†“ SecurityContext๋ฅผ Session์—์„œ ๋ณต์›
2. UsernamePasswordAuthenticationFilter
   โ†“ /login POST ์š”์ฒญ ์ฒ˜๋ฆฌ
3. BasicAuthenticationFilter
   โ†“ Basic Auth ํ—ค๋” ์ฒ˜๋ฆฌ
4. RememberMeAuthenticationFilter
   โ†“ Remember-me ์ฟ ํ‚ค ์ฒ˜๋ฆฌ
5. AnonymousAuthenticationFilter
   โ†“ ์ต๋ช… ์‚ฌ์šฉ์ž ์ฒ˜๋ฆฌ
6. ExceptionTranslationFilter
   โ†“ ์ธ์ฆ/์ธ๊ฐ€ ์˜ˆ์™ธ๋ฅผ HTTP ์‘๋‹ต์œผ๋กœ
7. FilterSecurityInterceptor
   โ†“ ์ตœ์ข… ์ธ๊ฐ€ ๊ฒฐ์ •

๊ฐ Filter์˜ ์—ญํ• :

SecurityContextPersistenceFilter

  • ์š”์ฒญ ์‹œ์ž‘: Session โ†’ SecurityContext ๋ณต์›
  • ์š”์ฒญ ์ข…๋ฃŒ: SecurityContext โ†’ Session ์ €์žฅ
  • โ†’ ์š”์ฒญ ์‚ฌ์ด์— ์ธ์ฆ ์ •๋ณด ์œ ์ง€

UsernamePasswordAuthenticationFilter โญ

  • POST /login ์š”์ฒญ ๊ฐ€๋กœ์ฑ”
  • ID/PW ์ถ”์ถœ โ†’ AuthenticationManager์— ์œ„์ž„
  • ์„ฑ๊ณต โ†’ SecurityContext์— ์ €์žฅ + ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ
  • ์‹คํŒจ โ†’ ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€๋กœ

ExceptionTranslationFilter

  • ๋’ค์ชฝ Filter์—์„œ ๋ฐœ์ƒํ•œ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ
  • AuthenticationException โ†’ ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€ (๋˜๋Š” 401)
  • AccessDeniedException โ†’ 403 ํŽ˜์ด์ง€

FilterSecurityInterceptor

  • ๊ฐ€์žฅ ๋งˆ์ง€๋ง‰ Filter
  • ์‹ค์ œ ์ธ๊ฐ€ ๊ฒฐ์ • (hasRole, hasAuthority ๋“ฑ)

JWT ์‚ฌ์šฉ ์‹œ:

  • UsernamePasswordAuthenticationFilter ๋Œ€์‹ 
  • ์ปค์Šคํ…€ JwtAuthenticationFilter ์ถ”๊ฐ€
  • Session ์‚ฌ์šฉ ์•ˆ ํ•จ (SessionCreationPolicy.STATELESS)
@Configuration
public class SecurityConfig {
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .sessionManagement(s -> s.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
            .addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class)
            // ...
        return http.build();
    }
}

์ž๊ธฐ ์ ๊ฒ€

  • ์™œ Filter ์ˆœ์„œ๊ฐ€ ์ค‘์š”ํ•œ๊ฐ€? (ํžŒํŠธ: ์˜์กด์„ฑ โ€” Context ๋ณต์› ํ›„ ์ธ์ฆ ์‹œ๋„)
  • ExceptionTranslationFilter๊ฐ€ ์—†์œผ๋ฉด? (ํžŒํŠธ: ๋ณด์•ˆ ์˜ˆ์™ธ๊ฐ€ ์ปจํŠธ๋กค๋Ÿฌ๊นŒ์ง€ ํ˜๋Ÿฌ๊ฐ)

Unit 2.3 โ€” SecurityContext์™€ SecurityContextHolder

์„ ์ˆ˜ ์ง€์‹: Unit 2.2, 4์ฃผ์ฐจ ThreadLocal

ํ•ต์‹ฌ ๊ฐœ๋…

SecurityContext:

  • ํ˜„์žฌ ์ธ์ฆ ์ •๋ณด๋ฅผ ๋‹ด๋Š” ์ปจํ…Œ์ด๋„ˆ
  • Authentication ๊ฐ์ฒด ๋ณด์œ 

SecurityContextHolder:

  • SecurityContext์— ์ ‘๊ทผํ•˜๋Š” ์ •์  ๋„์šฐ๋ฏธ
  • ThreadLocal ๊ธฐ๋ฐ˜ โญ (4์ฃผ์ฐจ ํ•™์Šต)
// ์–ด๋””์„œ๋‚˜ ํ˜„์žฌ ์‚ฌ์šฉ์ž ์ ‘๊ทผ ๊ฐ€๋Šฅ
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
String username = auth.getName();

์™œ ThreadLocal?:

  • ๊ฐ ์š”์ฒญ์€ ๋ณ„๋„ ์Šค๋ ˆ๋“œ
  • ์Šค๋ ˆ๋“œ๋ณ„๋กœ ๋‹ค๋ฅธ ์‚ฌ์šฉ์ž ์ •๋ณด ๋ณด๊ด€
  • ์ปจํŠธ๋กค๋Ÿฌ/์„œ๋น„์Šค ์–ด๋””์„œ๋‚˜ ์ ‘๊ทผ ๊ฐ€๋Šฅ

4์ฃผ์ฐจ ThreadLocal ํ•จ์ • ์žฌ๋“ฑ์žฅ โš ๏ธ :

  • ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ (@Async, CompletableFuture)์—์„œ SecurityContext ์†์‹ค
  • โ†’ DelegatingSecurityContextRunnable ๋“ฑ์œผ๋กœ ์ „ํŒŒ ํ•„์š”

Authentication ์ธํ„ฐํŽ˜์ด์Šค:

public interface Authentication extends Principal {
    Collection<? extends GrantedAuthority> getAuthorities();  // ๊ถŒํ•œ ๋ชฉ๋ก
    Object getCredentials();  // ๋น„๋ฐ€๋ฒˆํ˜ธ (๊ฒ€์ฆ ํ›„ ๋ณดํ†ต null)
    Object getDetails();      // ์ถ”๊ฐ€ ์ •๋ณด (IP ๋“ฑ)
    Object getPrincipal();    // ์‚ฌ์šฉ์ž ๋ณธ์ฒด (UserDetails)
    boolean isAuthenticated();
}

๊ตฌํ˜„์ฒด ์˜ˆ:

  • UsernamePasswordAuthenticationToken (form ๋กœ๊ทธ์ธ)
  • JwtAuthenticationToken (JWT)
  • OAuth2AuthenticationToken (OAuth2)

Spring Boot์—์„œ ํ˜„์žฌ ์‚ฌ์šฉ์ž ๊ฐ€์ ธ์˜ค๊ธฐ (์‹ค์šฉ):

๋ฐฉ๋ฒ• 1 โ€” SecurityContextHolder ์ง์ ‘:

Authentication auth = SecurityContextHolder.getContext().getAuthentication();

๋ฐฉ๋ฒ• 2 โ€” @AuthenticationPrincipal (๊ถŒ์žฅ) โญ :

@GetMapping("/me")
public User getCurrentUser(@AuthenticationPrincipal UserDetails userDetails) {
    return userService.findByUsername(userDetails.getUsername());
}

๋ฐฉ๋ฒ• 3 โ€” Authentication ์ง์ ‘ ์ฃผ์ž…:

@GetMapping("/me")
public User getCurrentUser(Authentication authentication) {
    return userService.findByUsername(authentication.getName());
}

์ž๊ธฐ ์ ๊ฒ€

  • ThreadLocal๋กœ ํ˜„์žฌ ์‚ฌ์šฉ์ž๋ฅผ ์ €์žฅํ•˜๋Š” ์œ„ํ—˜์€? (ํžŒํŠธ: ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜ โ€” request ์ข…๋ฃŒ ์‹œ clear ํ•„์ˆ˜)
  • @Async ๋ฉ”์„œ๋“œ์—์„œ SecurityContextHolder๊ฐ€ ๋น„์–ด์žˆ์œผ๋ฉด? (ํžŒํŠธ: ์ „ํŒŒ ์„ค์ • ํ•„์š”)

๐Ÿ“š Phase 3 โ€” ์ธ์ฆ ์ฒ˜๋ฆฌ ํ๋ฆ„

๋ชฉํ‘œ: Spring Security๊ฐ€ ID/PW๋ฅผ ์–ด๋–ป๊ฒŒ ๊ฒ€์ฆํ•˜๋Š”์ง€, ํ๋ฆ„์„ ๋”ฐ๋ผ๊ฐ„๋‹ค.

Unit 3.1 โ€” UserDetails์™€ UserDetailsService

์„ ์ˆ˜ ์ง€์‹: Phase 2

ํ•ต์‹ฌ ์ธํ„ฐํŽ˜์ด์Šค

UserDetails โ€” ์‚ฌ์šฉ์ž ์ •๋ณด:

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

๊ตฌํ˜„ ์˜ˆ์‹œ:

@Getter
public class CustomUserDetails implements UserDetails {
    private final User user;  // JPA ์—”ํ‹ฐํ‹ฐ
    
    @Override
    public String getUsername() { return user.getEmail(); }
    
    @Override
    public String getPassword() { return user.getPasswordHash(); }
    
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return user.getRoles().stream()
            .map(role -> new SimpleGrantedAuthority("ROLE_" + role.getName()))
            .toList();
    }
    
    @Override
    public boolean isAccountNonLocked() { return !user.isLocked(); }
    // ... ๋‚˜๋จธ์ง€
}

UserDetailsService โ€” ์‚ฌ์šฉ์ž ์กฐํšŒ:

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

๊ตฌํ˜„ ์˜ˆ์‹œ:

@Service
@RequiredArgsConstructor
public class CustomUserDetailsService implements UserDetailsService {
    private final UserRepository userRepository;
    
    @Override
    public UserDetails loadUserByUsername(String email) {
        User user = userRepository.findByEmail(email)
            .orElseThrow(() -> new UsernameNotFoundException("User not found: " + email));
        return new CustomUserDetails(user);
    }
}

โ†’ Spring Security๊ฐ€ ์ž๋™์œผ๋กœ ์ด ๊ตฌํ˜„์ฒด๋ฅผ ์‚ฌ์šฉ


์™œ ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๋ถ„๋ฆฌํ–ˆ๋‚˜ (5์ฃผ์ฐจ OCP, DI):

  • DB๊ฐ€ ์•„๋‹Œ LDAP, ์™ธ๋ถ€ API์—์„œ๋„ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ๋„๋ก
  • ํ…Œ์ŠคํŠธ ์‹œ In-Memory ๊ตฌํ˜„ ๊ฐ€๋Šฅ

์ž๊ธฐ ์ ๊ฒ€

  • ์™œ password๋ฅผ UserDetails์— ๋…ธ์ถœ์‹œํ‚ค๋‚˜? (ํžŒํŠธ: AuthenticationProvider ๊ฒ€์ฆ์šฉ โ€” ์‘๋‹ต์—๋Š” ์ ˆ๋Œ€ ๋…ธ์ถœ X)
  • 5์ฃผ์ฐจ์˜ ์–ด๋–ค ํŒจํ„ด์ธ๊ฐ€? (ํžŒํŠธ: Strategy)

Unit 3.2 โ€” AuthenticationManager์™€ AuthenticationProvider โญ

์„ ์ˆ˜ ์ง€์‹: Unit 3.1

ํ•ต์‹ฌ ํ๋ฆ„

AuthenticationManager โ€” ์ธ์ฆ ์ฑ…์ž„์ž:

  • ์ธ์ฆ ์š”์ฒญ ๋ฐ›์Œ
  • ์ ์ ˆํ•œ Provider์— ์œ„์ž„
  • ๋ณดํ†ต ProviderManager ๊ตฌํ˜„์ฒด ์‚ฌ์šฉ

AuthenticationProvider โ€” ์‹ค์ œ ๊ฒ€์ฆ:

  • ํŠน์ • ์ธ์ฆ ๋ฐฉ์‹ ์ฒ˜๋ฆฌ (DB, LDAP, OAuth2 ๋“ฑ)
  • ID/PW ๊ฒ€์ฆ, ๊ถŒํ•œ ๋ถ€์—ฌ

์ „์ฒด ์ธ์ฆ ํ๋ฆ„ โญ :

1. UsernamePasswordAuthenticationFilter
   โ†“ ์š”์ฒญ์—์„œ ID/PW ์ถ”์ถœ
   โ†“ UsernamePasswordAuthenticationToken ์ƒ์„ฑ (๋ฏธ์ธ์ฆ)
   
2. AuthenticationManager.authenticate(token)
   โ†“ ProviderManager๊ฐ€ Provider ๋ชฉ๋ก์—์„œ ์„ ํƒ
   
3. DaoAuthenticationProvider (๋Œ€ํ‘œ Provider)
   โ†“ UserDetailsService.loadUserByUsername() ํ˜ธ์ถœ
   โ†“ DB์—์„œ UserDetails ๊ฐ€์ ธ์˜ด
   โ†“ PasswordEncoder.matches(rawPassword, hashedPassword) ๊ฒ€์ฆ
   โ†“ ์„ฑ๊ณต โ†’ UsernamePasswordAuthenticationToken (์ธ์ฆ๋จ) ๋ฐ˜ํ™˜
   
4. SecurityContextHolder์— ์ €์žฅ
   
5. ํ›„์† ์š”์ฒญ์—์„œ ์ž๋™ ์ธ์ฆ

PasswordEncoder โญ :

  • ํ‰๋ฌธ ๋น„๋ฐ€๋ฒˆํ˜ธ ์ €์žฅ X (๋ณด์•ˆ ์‚ฌ๊ณ )
  • ํ•ด์‹œ(Hash) + Salt ๋กœ ์ €์žฅ
  • ๊ฒ€์ฆ ์‹œ ๊ฐ™์€ ๋ฐฉ์‹์œผ๋กœ ํ•ด์‹œ ํ›„ ๋น„๊ต

๋Œ€ํ‘œ ๊ตฌํ˜„์ฒด:

  • BCryptPasswordEncoder (๊ฐ€์žฅ ํ”ํ•จ, ๊ถŒ์žฅ) โญ
  • Argon2PasswordEncoder (๋” ์•ˆ์ „)
  • Pbkdf2PasswordEncoder
  • โŒ MD5, SHA-1 (์ทจ์•ฝ โ€” ์ ˆ๋Œ€ X)
@Bean
public PasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder(12);  // strength
}

// ์‚ฌ์šฉ
String hash = passwordEncoder.encode("rawPassword");
// โ†’ "$2a$12$abc..." ๊ฐ™์€ ํ˜•ํƒœ

boolean match = passwordEncoder.matches("rawPassword", hash);

BCrypt์˜ ํŠน์ง•:

  • Salt ์ž๋™ ์ฒ˜๋ฆฌ (ํ•ด์‹œ ์•ˆ์— ํฌํ•จ)
  • ๋А๋ฆผ (์˜๋„์ ) โ€” ๋ฌด์ฐจ๋ณ„ ๋Œ€์ž… ๊ณต๊ฒฉ ๋ฐฉ์–ด
  • strength ์กฐ์ • ๊ฐ€๋Šฅ (๋†’์„์ˆ˜๋ก ๋А๋ฆผ + ์•ˆ์ „)

์ปค์Šคํ…€ AuthenticationProvider:

@Component
public class CustomAuthProvider implements AuthenticationProvider {
    @Override
    public Authentication authenticate(Authentication auth) {
        // ์ปค์Šคํ…€ ๊ฒ€์ฆ ๋กœ์ง (์˜ˆ: ์™ธ๋ถ€ API ํ˜ธ์ถœ)
    }
    
    @Override
    public boolean supports(Class<?> authentication) {
        return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
    }
}

ILIC ์ ์šฉ:

  • ์ผ๋ฐ˜ ์‚ฌ์šฉ์ž: DaoAuthenticationProvider + BCrypt
  • ์™ธ๋ถ€ ํŒŒํŠธ๋„ˆ: ์ปค์Šคํ…€ Provider (API Key ๊ฒ€์ฆ)
  • ๊ด€๋ฆฌ์ž: LDAP ์—ฐ๋™ ๊ฐ€๋Šฅ

์ž๊ธฐ ์ ๊ฒ€

  • BCrypt์˜ strength๊ฐ€ ๋†’์„์ˆ˜๋ก ์ข‹์€๊ฐ€? (ํžŒํŠธ: ๋ณด์•ˆ โ†‘ ๊ทธ๋Ÿฌ๋‚˜ ์‘๋‹ต ์‹œ๊ฐ„ โ†‘ โ€” ๊ท ํ˜•)
  • ๊ฐ™์€ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์—ฌ๋Ÿฌ ๋ฒˆ ํ•ด์‹œํ•ด๋„ ๋‹ค๋ฅธ ๊ฒฐ๊ณผ๊ฐ€ ๋‚˜์˜ค๋Š” ์ด์œ ๋Š”? (ํžŒํŠธ: Salt ์ž๋™ ์ถ”๊ฐ€)

Unit 3.3 โ€” Form ๋กœ๊ทธ์ธ vs API ๋กœ๊ทธ์ธ

์„ ์ˆ˜ ์ง€์‹: Unit 3.2

Form ๋กœ๊ทธ์ธ (์ „ํ†ต ์›น):

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http
        .formLogin(form -> form
            .loginPage("/login")
            .loginProcessingUrl("/login")  // POST ์ฒ˜๋ฆฌ URL
            .defaultSuccessUrl("/home")
            .failureUrl("/login?error")
        );
    return http.build();
}

ํ๋ฆ„:
1. GET /login โ†’ ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€ ํ‘œ์‹œ
2. POST /login (ID/PW) โ†’ UsernamePasswordAuthenticationFilter ๊ฐ€๋กœ์ฑ”
3. ์„ฑ๊ณต โ†’ /home ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ + Session ์ƒ์„ฑ
4. ์‹คํŒจ โ†’ /login?error ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ


API ๋กœ๊ทธ์ธ (REST + JWT):

@RestController
@RequiredArgsConstructor
public class AuthController {
    private final AuthenticationManager authManager;
    private final JwtTokenProvider jwtProvider;
    
    @PostMapping("/api/login")
    public LoginResponse login(@RequestBody LoginRequest request) {
        // 1. ์ธ์ฆ ์‹œ๋„
        Authentication auth = authManager.authenticate(
            new UsernamePasswordAuthenticationToken(
                request.getEmail(), request.getPassword()
            )
        );
        
        // 2. JWT ์ƒ์„ฑ
        String accessToken = jwtProvider.createAccessToken(auth);
        String refreshToken = jwtProvider.createRefreshToken(auth);
        
        return new LoginResponse(accessToken, refreshToken);
    }
}

ํ๋ฆ„:
1. POST /api/login (JSON ID/PW)
2. AuthenticationManager๋กœ ์ง์ ‘ ์ธ์ฆ
3. JWT ์ƒ์„ฑ ํ›„ ์‘๋‹ต
4. ํด๋ผ์ด์–ธํŠธ๊ฐ€ JWT ์ €์žฅ (LocalStorage / HttpOnly Cookie)
5. ์ดํ›„ ์š”์ฒญ์— Authorization: Bearer ...


ILIC ์‹œ๋‚˜๋ฆฌ์˜ค:

  • Vue 3 SPA โ†’ API ๋กœ๊ทธ์ธ + JWT
  • ๊ด€๋ฆฌ ํŽ˜์ด์ง€ (๊ฐ„๋‹จ) โ†’ Form ๋กœ๊ทธ์ธ ๊ฐ€๋Šฅ
  • ๋ชจ๋ฐ”์ผ ์•ฑ โ†’ API + JWT

์ž๊ธฐ ์ ๊ฒ€

  • API ๋กœ๊ทธ์ธ์—์„œ ์™œ AuthenticationManager๋ฅผ ์ง์ ‘ ํ˜ธ์ถœ? (ํžŒํŠธ: Filter๊ฐ€ ์•„๋‹Œ ์ปจํŠธ๋กค๋Ÿฌ์—์„œ)
  • Form ๋กœ๊ทธ์ธ์„ SPA์—์„œ ์“ธ ์ˆ˜ ์žˆ๋Š”๊ฐ€? (ํžŒํŠธ: ๊ฐ€๋Šฅํ•˜์ง€๋งŒ ๋ถ€์ž์—ฐ์Šค๋Ÿฌ์›€)

๐Ÿ“š Phase 4 โ€” ์ธ๊ฐ€ ์ฒ˜๋ฆฌ

๋ชฉํ‘œ: URL ๊ธฐ๋ฐ˜๊ณผ ๋ฉ”์„œ๋“œ ๊ธฐ๋ฐ˜ ์ธ๊ฐ€์˜ ์ฐจ์ด์™€ ํ™œ์šฉ์„ ๋งˆ์Šคํ„ฐํ•œ๋‹ค.

Unit 4.1 โ€” HttpSecurity๋กœ URL ๊ธฐ๋ฐ˜ ์ธ๊ฐ€

์„ ์ˆ˜ ์ง€์‹: Phase 3

ํ•ต์‹ฌ ํŒจํ„ด

Spring Security 6 (ํ˜„๋Œ€):

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http
        .authorizeHttpRequests(auth -> auth
            .requestMatchers("/public/**").permitAll()
            .requestMatchers("/api/auth/**").permitAll()
            .requestMatchers("/api/admin/**").hasRole("ADMIN")
            .requestMatchers(HttpMethod.GET, "/api/products/**").permitAll()
            .requestMatchers(HttpMethod.POST, "/api/products/**").hasRole("ADMIN")
            .anyRequest().authenticated()
        );
    return http.build();
}

์ฃผ์š” ๋ฉ”์„œ๋“œ:

๋ฉ”์„œ๋“œ์˜๋ฏธ
permitAll()๋ˆ„๊ตฌ๋‚˜ ์ ‘๊ทผ
denyAll()๋ชจ๋‘ ๊ฑฐ๋ถ€
authenticated()์ธ์ฆ๋œ ์‚ฌ์šฉ์ž
hasRole("ADMIN")ํŠน์ • ์—ญํ• 
hasAuthority("READ_PRODUCT")ํŠน์ • ๊ถŒํ•œ
hasAnyRole("ADMIN", "MANAGER")์—ฌ๋Ÿฌ ์—ญํ•  ์ค‘ ํ•˜๋‚˜
access("hasRole('ADMIN') and hasIpAddress('192.168.1.0/24')")SpEL ๋ณต์žก ํ‘œํ˜„

Role vs Authority โญ :

RoleAuthority
์˜๋ฏธ์—ญํ•  ๊ทธ๋ฃน์„ธ๋ถ€ ๊ถŒํ•œ
์˜ˆROLE_ADMIN, ROLE_USERREAD_PRODUCT, DELETE_USER
๋ฉ”์„œ๋“œhasRole("ADMIN")hasAuthority("READ_PRODUCT")
Prefix์ž๋™ ROLE_ ์ถ”๊ฐ€๊ทธ๋Œ€๋กœ

ํ•ต์‹ฌ:

  • hasRole("ADMIN") โ†” DB์— ROLE_ADMIN ์ €์žฅ
  • hasAuthority("ROLE_ADMIN") โ†” DB์— ROLE_ADMIN ์ €์žฅ (๊ฐ™์€ ๊ฒฐ๊ณผ)

ILIC ์‹œ๋‚˜๋ฆฌ์˜ค:

.authorizeHttpRequests(auth -> auth
    .requestMatchers("/api/auth/**").permitAll()
    .requestMatchers("/api/fares/**").hasAnyRole("USER", "PARTNER", "ADMIN")
    .requestMatchers("/api/admin/**").hasRole("ADMIN")
    .requestMatchers(HttpMethod.DELETE, "/api/**").hasRole("ADMIN")
    .anyRequest().authenticated()
)

์ˆœ์„œ๊ฐ€ ์ค‘์š” โš ๏ธ :

  • ์œ„์—์„œ๋ถ€ํ„ฐ ๋งค์นญ โ†’ ์ฒซ ๋งค์นญ ์ ์šฉ
  • ๊ตฌ์ฒด์  ํŒจํ„ด โ†’ ์ผ๋ฐ˜ ํŒจํ„ด ์ˆœ์œผ๋กœ

์ž๊ธฐ ์ ๊ฒ€

  • /api/admin/users ์š”์ฒญ์„ ์ผ๋ฐ˜ ์‚ฌ์šฉ์ž๊ฐ€ ํ–ˆ๋‹ค๋ฉด ์–ด๋–ค ์‘๋‹ต? (ํžŒํŠธ: 403)
  • /api/admin/users ์š”์ฒญ์„ ์ธ์ฆ ์—†์ด ํ–ˆ๋‹ค๋ฉด? (ํžŒํŠธ: 401)

Unit 4.2 โ€” ๋ฉ”์„œ๋“œ ๋ณด์•ˆ (@PreAuthorize, @PostAuthorize) โญ

์„ ์ˆ˜ ์ง€์‹: Unit 4.1, 8-9์ฃผ์ฐจ AOP

ํ•ต์‹ฌ ๊ฐœ๋…

๋ฉ”์„œ๋“œ ๋ณด์•ˆ:

  • URL ํŒจํ„ด์ด ์•„๋‹Œ ๋ฉ”์„œ๋“œ ๋‹จ์œ„ ์ธ๊ฐ€
  • AOP ๊ธฐ๋ฐ˜ (8-9์ฃผ์ฐจ ํ•™์Šต ํ™œ์šฉ)

ํ™œ์„ฑํ™”:

@Configuration
@EnableMethodSecurity
public class SecurityConfig { ... }

@PreAuthorize โ€” ๋ฉ”์„œ๋“œ ํ˜ธ์ถœ ์ „ ๊ฒ€์ฆ:

@PreAuthorize("hasRole('ADMIN')")
public void deleteUser(Long userId) { ... }

@PreAuthorize("#userId == authentication.principal.id or hasRole('ADMIN')")
public User getUser(Long userId) { ... }

ํ•ต์‹ฌ:

  • SpEL ์‚ฌ์šฉ ๊ฐ€๋Šฅ
  • ๋ฉ”์„œ๋“œ ์ธ์ž ์ฐธ์กฐ (#userId)
  • authentication ๋ณ€์ˆ˜๋กœ ํ˜„์žฌ ์‚ฌ์šฉ์ž

@PostAuthorize โ€” ๋ฉ”์„œ๋“œ ํ˜ธ์ถœ ํ›„ ๊ฒ€์ฆ:

@PostAuthorize("returnObject.owner == authentication.principal.username")
public Document getDocument(Long id) { ... }

โ†’ ๋ฐ˜ํ™˜๋œ ๊ฐ์ฒด ๊ฒ€์‚ฌ ํ›„ ๊ถŒํ•œ ๊ฒฐ์ •


@PreFilter / @PostFilter โ€” ์ปฌ๋ ‰์…˜ ํ•„ํ„ฐ๋ง:

@PostFilter("filterObject.owner == authentication.principal.username")
public List<Document> getMyDocuments() {
    return documentRepository.findAll();  // ๋ชจ๋‘ ๊ฐ€์ ธ์˜จ ํ›„ ํ•„ํ„ฐ
}

โš ๏ธ ์ฃผ์˜: ์„ฑ๋Šฅ โ€” DB์—์„œ ๋ชจ๋‘ ๊ฐ€์ ธ์™€์„œ ํ•„ํ„ฐ๋ง

โ†’ ๊ฐ€๋Šฅํ•˜๋ฉด ์ฟผ๋ฆฌ ๋‹จ๊ณ„์—์„œ ํ•„ํ„ฐ๋ง


๋ฉ”์„œ๋“œ ๋ณด์•ˆ์˜ ๋ณธ์งˆ โญ :

  • 8-9์ฃผ์ฐจ AOP ๋ฉ”์ปค๋‹ˆ์ฆ˜
  • @Around advice๋กœ ๋ฉ”์„œ๋“œ ๊ฐ€๋กœ์ฑ”
  • โ†’ ์ž๊ธฐ ํ˜ธ์ถœ ํ•จ์ • ๋™์ผ (๊ฐ™์€ ํด๋ž˜์Šค ๋‚ด ํ˜ธ์ถœ ์‹œ ์•ˆ ๋จ)

ILIC ํ™œ์šฉ:

@Service
public class FareService {
    
    @PreAuthorize("hasRole('USER')")
    public List<Fare> findAllByCurrentUser() { ... }
    
    @PreAuthorize("hasRole('ADMIN')")
    public void delete(Long id) { ... }
    
    @PreAuthorize("@fareSecurity.canEdit(#fareId, authentication)")
    public Fare update(Long fareId, FareDto dto) { ... }
}

@Component("fareSecurity")
public class FareSecurity {
    public boolean canEdit(Long fareId, Authentication auth) {
        // ๋ณต์žกํ•œ ๊ถŒํ•œ ๋กœ์ง (์†Œ์œ ์ž ๋˜๋Š” ๊ด€๋ฆฌ์ž)
    }
}

์ž๊ธฐ ์ ๊ฒ€

  • URL ์ธ๊ฐ€ vs ๋ฉ”์„œ๋“œ ์ธ๊ฐ€์˜ ๊ฒฐ์ •์  ์ฐจ์ด๋Š”? (ํžŒํŠธ: ์–ด๋””์— ๋ช…์‹œ โ€” ๊ธ€๋กœ๋ฒŒ vs ๋ฉ”์„œ๋“œ)
  • 8-9์ฃผ์ฐจ์˜ ์–ด๋–ค ํ•จ์ •์ด ๋ฉ”์„œ๋“œ ๋ณด์•ˆ์—๋„ ์ ์šฉ๋˜๋Š”๊ฐ€? (ํžŒํŠธ: ์ž๊ธฐ ํ˜ธ์ถœ, @PostConstruct)

๐Ÿ“š Phase 5 โ€” Session ๊ธฐ๋ฐ˜ vs Token ๊ธฐ๋ฐ˜ (โ˜… ์ •์  2 โ€” ๋ฉด์ ‘ ๋‹จ๊ณจ)

๋ชฉํ‘œ: ๋ฉด์ ‘์—์„œ ๊ฑฐ์˜ 100% ์ถœ์ œ๋˜๋Š” ๋น„๊ต๋ฅผ ๋ช…ํ™•ํžˆ ์žก๋Š”๋‹ค.

Unit 5.1 โ€” Session ๊ธฐ๋ฐ˜ ์ธ์ฆ์˜ ๋™์ž‘ ์›๋ฆฌ

์„ ์ˆ˜ ์ง€์‹: Phase 2

ํ•ต์‹ฌ ํ๋ฆ„

1. [Login Request] POST /login (id, pw)
        โ†“
2. [Server] ๊ฒ€์ฆ โ†’ Session ์ƒ์„ฑ
   - Session ID (๋žœ๋ค ๋ฌธ์ž์—ด) ์ƒ์„ฑ
   - Server ๋ฉ”๋ชจ๋ฆฌ ๋˜๋Š” Redis์— Session ์ €์žฅ
   - Session ๋ฐ์ดํ„ฐ: { user_id: 42, role: "USER" }
        โ†“
3. [Response] Set-Cookie: SESSIONID=abc123; HttpOnly; Secure
        โ†“
4. [Browser] Cookie ์ž๋™ ์ €์žฅ
        โ†“
5. [Subsequent Request] Cookie: SESSIONID=abc123
        โ†“
6. [Server] Session ID๋กœ Session ์กฐํšŒ โ†’ ์‚ฌ์šฉ์ž ์‹๋ณ„

Session ์ €์žฅ์†Œ:

1. ์„œ๋ฒ„ ๋ฉ”๋ชจ๋ฆฌ:

  • ๋น ๋ฆ„
  • ์„œ๋ฒ„ ์žฌ์‹œ์ž‘ ์‹œ ๋ชจ๋‘ ์†์‹ค
  • ๋‹ค์ค‘ ์„œ๋ฒ„์—์„œ ๊ณต์œ  X โš ๏ธ

2. Redis (๋ถ„์‚ฐ) โญ :

  • 16-17์ฃผ์ฐจ์˜ ์ž์—ฐ์Šค๋Ÿฌ์šด ํ™œ์šฉ
  • ๋‹ค์ค‘ ์„œ๋ฒ„ ๊ณต์œ  ๊ฐ€๋Šฅ
  • ์˜์†์„ฑ
spring:
  session:
    store-type: redis
  redis:
    host: localhost
    port: 6379
@EnableRedisHttpSession
@Configuration
public class SessionConfig { }

3. JDBC:

  • DB์— Session ์ €์žฅ
  • ๋А๋ฆผ, ์ž˜ ์•ˆ ์”€

Cookie ๋ณด์•ˆ ์†์„ฑ โญ :

์†์„ฑ์˜๋ฏธ
HttpOnlyJavaScript์—์„œ ์ ‘๊ทผ X (XSS ๋ฐฉ์–ด)
SecureHTTPS์—์„œ๋งŒ ์ „์†ก
SameSite๋‹ค๋ฅธ ๋„๋ฉ”์ธ ์š”์ฒญ ์‹œ ์ฐจ๋‹จ (CSRF ๋ฐฉ์–ด)
Path์ ์šฉ ๊ฒฝ๋กœ
Max-Age๋งŒ๋ฃŒ ์‹œ๊ฐ„

๋ชจ๋ฒ” ์‚ฌ๋ก€:

Set-Cookie: SESSIONID=abc; HttpOnly; Secure; SameSite=Lax; Max-Age=3600

Session์˜ ์žฅ์  โญ :
1. ์ฆ‰์‹œ ๋ฌดํšจํ™” ๊ฐ€๋Šฅ โ€” ์„œ๋ฒ„์—์„œ ์‚ญ์ œํ•˜๋ฉด ๋
2. ๋ฏผ๊ฐ ์ •๋ณด ์„œ๋ฒ„ ๋ณด๊ด€ โ€” Cookie์—๋Š” ID๋งŒ
3. ๋‹จ์ˆœ โ€” Spring ๊ธฐ๋ณธ

Session์˜ ๋‹จ์  โš ๏ธ :
1. ์„œ๋ฒ„ ์ƒํƒœ ๋ณด์œ  (Stateful) โ€” ๋ถ„์‚ฐ ํ™˜๊ฒฝ ๋ถ€๋‹ด
2. ์ˆ˜ํ‰ ํ™•์žฅ ์–ด๋ ค์›€ โ€” Sticky Session ๋˜๋Š” ๊ณต์œ  ์ €์žฅ์†Œ ํ•„์š”
3. CSRF ๊ณต๊ฒฉ ์œ„ํ—˜ (์ž๋™ Cookie ์ „์†ก)
4. ๋ชจ๋ฐ”์ผ ์•ฑ๊ณผ ๋ถ€์ž์—ฐ์Šค๋Ÿฌ์›€

์ž๊ธฐ ์ ๊ฒ€

  • ์„œ๋ฒ„ 3๋Œ€์— Session์„ ๊ณต์œ ํ•˜๋Š” ๋ฐฉ๋ฒ• 2๊ฐ€์ง€๋Š”? (ํžŒํŠธ: Sticky Session, Redis ๊ณต์œ )
  • HttpOnly Cookie์™€ ์ผ๋ฐ˜ Cookie์˜ ๋ณด์•ˆ ์ฐจ์ด๋Š”? (ํžŒํŠธ: XSS)

Unit 5.2 โ€” Token ๊ธฐ๋ฐ˜ ์ธ์ฆ (JWT ๋“ฑ) ๋™์ž‘ ์›๋ฆฌ

์„ ์ˆ˜ ์ง€์‹: Unit 5.1

ํ•ต์‹ฌ ํ๋ฆ„

1. [Login Request] POST /api/login (email, pw)
        โ†“
2. [Server] ๊ฒ€์ฆ โ†’ JWT ์ƒ์„ฑ
   - Header + Payload + Signature
   - Payload: { sub: 42, role: "USER", exp: 1700000000 }
   - Signature: ์„œ๋ฒ„ ๋น„๋ฐ€ํ‚ค๋กœ ์„œ๋ช…
        โ†“
3. [Response] { "accessToken": "eyJhbGc..." }
        โ†“
4. [Browser/App] ํ† ํฐ ์ €์žฅ
   - LocalStorage / SessionStorage / HttpOnly Cookie
        โ†“
5. [Subsequent Request] Authorization: Bearer eyJhbGc...
        โ†“
6. [Server] JWT ๊ฒ€์ฆ (์„œ๋ช…๋งŒ ํ™•์ธ)
   - ์„œ๋ฒ„ ๋ฉ”๋ชจ๋ฆฌ ์กฐํšŒ X (Stateless!)
   - Payload์—์„œ ์‚ฌ์šฉ์ž ID ์ถ”์ถœ

ํ•ต์‹ฌ ์ฐจ์ด โ€” ์„œ๋ฒ„ ์ƒํƒœ:

Session:

  • ์„œ๋ฒ„: { sessionId123: userInfo } ๋ณด๊ด€
  • ๋งค ์š”์ฒญ๋งˆ๋‹ค ์„œ๋ฒ„ ์กฐํšŒ

JWT:

  • ์„œ๋ฒ„: ์•„๋ฌด๊ฒƒ๋„ ๋ณด๊ด€ X
  • JWT ์ž์ฒด์— ์‚ฌ์šฉ์ž ์ •๋ณด + ์„œ๋ช…
  • โ†’ Stateless โญ

JWT์˜ ์žฅ์  โญ :
1. Stateless โ€” ์„œ๋ฒ„ ํ™•์žฅ ์ž์œ 
2. MSA ์นœํ™” โ€” ์„œ๋น„์Šค ๊ฐ„ ํ† ํฐ ์ „๋‹ฌ ์‰ฌ์›€
3. ๋‹ค์–‘ํ•œ ํด๋ผ์ด์–ธํŠธ โ€” ๋ชจ๋ฐ”์ผ, IoT ๋“ฑ
4. SSO ๊ฐ€๋Šฅ โ€” ํ† ํฐ์„ ์—ฌ๋Ÿฌ ์„œ๋น„์Šค์—์„œ ์ธ์ •

JWT์˜ ๋‹จ์  โš ๏ธ :
1. ์ฆ‰์‹œ ํ๊ธฐ ์–ด๋ ค์›€ โ€” ๋งŒ๋ฃŒ ์ „๊นŒ์ง€ ์œ ํšจ
2. ํ† ํฐ ํฌ๊ธฐ โ†‘ (Header์— ๋งค๋ฒˆ ์ „์†ก)
3. ๋ณด์•ˆ ์„ค์ • ์–ด๋ ค์›€ โ€” ์ €์žฅ ์œ„์น˜, ๋งŒ๋ฃŒ ๋“ฑ
4. ๋ฏผ๊ฐ ์ •๋ณด ๋…ธ์ถœ โ€” Payload๋Š” Base64 (์•”ํ˜ธํ™” X)


์ฆ‰์‹œ ํ๊ธฐ ๋ฌธ์ œ ํ•ด๊ฒฐ:

1. Short-lived Access Token + Refresh Token:

  • Access Token: 15๋ถ„ ~ 1์‹œ๊ฐ„ (์งง๊ฒŒ)
  • Refresh Token: 1~30์ผ (๊ธธ๊ฒŒ)
  • ๋งŒ๋ฃŒ ์‹œ Refresh๋กœ ์žฌ๋ฐœ๊ธ‰

2. Token Blacklist:

  • ํ๊ธฐ๋œ ํ† ํฐ์„ Redis์— ์ €์žฅ
  • ๋งค ์š”์ฒญ๋งˆ๋‹ค ํ™•์ธ
  • โ†’ Stateless ์˜ ์˜๋ฏธ ์•ฝํ™” โš ๏ธ

3. ์งง์€ ๋งŒ๋ฃŒ ์‹œ๊ฐ„ (15๋ถ„ ์ •๋„) + ๋น„๋ฐ€๋ฒˆํ˜ธ ๋ณ€๊ฒฝ ์‹œ ๋ชจ๋“  ํ† ํฐ ๋ฌดํšจ:

  • ๊ฐ€์žฅ ์‹ค์šฉ์  โญ

์ž๊ธฐ ์ ๊ฒ€

  • "JWT๋Š” ์ง„์งœ Stateless์ธ๊ฐ€?" (ํžŒํŠธ: Blacklist ์“ฐ๋ฉด NO)
  • Refresh Token๋„ JWT์—ฌ์•ผ ํ•˜๋‚˜? (ํžŒํŠธ: ๋ณดํ†ต์€ ๋‹จ์ˆœ ๋žœ๋ค ๋ฌธ์ž์—ด + DB ์ €์žฅ โ€” ์ฆ‰์‹œ ํ๊ธฐ ๊ฐ€๋Šฅ)

Unit 5.3 โ€” Session vs Token ๋น„๊ต ๋งคํŠธ๋ฆญ์Šค โญโญ (๋ฉด์ ‘ ํ•ต์‹ฌ)

์„ ์ˆ˜ ์ง€์‹: Unit 5.1, 5.2

์™„์ „ ๋น„๊ต โญ :

์ธก๋ฉดSessionJWT
์ƒํƒœStateful (์„œ๋ฒ„ ๋ณด๊ด€)Stateless
์ €์žฅ ์œ„์น˜์„œ๋ฒ„ ๋ฉ”๋ชจ๋ฆฌ/Redisํด๋ผ์ด์–ธํŠธ
ํ™•์žฅ์„ฑSticky ๋˜๋Š” ๊ณต์œ  ์ €์žฅ์†Œ์ž์œ 
์ฆ‰์‹œ ํ๊ธฐโœ… ๊ฐ€๋ŠฅโŒ ์–ด๋ ค์›€
๊ณต๊ฒฉ ๋ฐฉ์–ดCSRF ์œ„ํ—˜XSS ์œ„ํ—˜ (์ €์žฅ ์œ„์น˜ ๋”ฐ๋ผ)
๋ชจ๋ฐ”์ผ๋ถ€์ž์—ฐ์Šค๋Ÿฌ์›€์ž์—ฐ์Šค๋Ÿฌ์›€
MSA๋ถ€์ ํ•ฉ์ ํ•ฉ
ํŠธ๋ž˜ํ”ฝDB ์กฐํšŒ โ†‘๊ฒ€์ฆ๋งŒ
ํฌ๊ธฐCookie ID๋งŒ๋งค ์š”์ฒญ ํ† ํฐ ์ „์†ก

์„ ํƒ ๊ฐ€์ด๋“œ โญ :

์‹œ๋‚˜๋ฆฌ์˜ค์ถ”์ฒœ
์ „ํ†ต ์›น ๋ชจ๋†€๋ฆฌ์‹, B2CSession (๋‹จ์ˆœ)
SPA (React/Vue) + REST APIJWT
๋ชจ๋ฐ”์ผ ์•ฑJWT
MSA / ๋ถ„์‚ฐ ์‹œ์Šคํ…œJWT
๊ธˆ์œต / ์ฆ‰์‹œ ์ฐจ๋‹จ ํ•„์ˆ˜Session ๋˜๋Š” ์งง์€ JWT
SSO ํ•„์š”JWT (๋˜๋Š” OAuth2)

ILIC ์‹œ๋‚˜๋ฆฌ์˜ค ๋ถ„์„:

  • Vue 3 SPA + REST API + B2B โ†’ JWT ์ž์—ฐ์Šค๋Ÿฌ์›€
  • ๊ทธ๋Ÿฌ๋‚˜ ๊ฒฐ์ œ/๊ณ„์•ฝ ์ •๋ณด โ†’ ์ฆ‰์‹œ ํ๊ธฐ ๊ฐ€๋Šฅ์„ฑ ์ค‘์š” โ†’ ์งง์€ JWT + Refresh

๋ฉด์ ‘ ๋ชจ์˜ ๋‹ต๋ณ€ (3๋ถ„ ๋‹ต๋ณ€ ์ค€๋น„) โญ :

"Session๊ณผ JWT์˜ ๊ฐ€์žฅ ํฐ ์ฐจ์ด๋Š” ์ƒํƒœ ๋ณด์œ  ์—ฌ๋ถ€์ž…๋‹ˆ๋‹ค.

Session์€ ์„œ๋ฒ„์— ์ธ์ฆ ์ •๋ณด๋ฅผ ๋ณด๊ด€ํ•ด์„œ ์ฆ‰์‹œ ํ๊ธฐ๊ฐ€ ๊ฐ€๋Šฅํ•˜์ง€๋งŒ, ๋ถ„์‚ฐ ํ™˜๊ฒฝ์—์„œ๋Š” ์„œ๋ฒ„ ๊ฐ„ ๊ณต์œ ๋ฅผ ์œ„ํ•ด Redis ๊ฐ™์€ ์™ธ๋ถ€ ์ €์žฅ์†Œ๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.

JWT๋Š” ํ† ํฐ ์ž์ฒด์— ์‚ฌ์šฉ์ž ์ •๋ณด๊ฐ€ ๋‹ด๊ฒจ ์„œ๋ฒ„๋Š” ์„œ๋ช…๋งŒ ๊ฒ€์ฆํ•˜๋ฉด ๋˜๋Š” Stateless ๋ฐฉ์‹์ž…๋‹ˆ๋‹ค. ํ™•์žฅ์„ฑ์ด ์ข‹๊ณ  ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค์— ์ ํ•ฉํ•˜์ง€๋งŒ, ์ฆ‰์‹œ ํ๊ธฐ๊ฐ€ ์–ด๋ ค์›Œ์„œ ์งง์€ ๋งŒ๋ฃŒ ์‹œ๊ฐ„ + Refresh Token ํŒจํ„ด์„ ๋ณดํ†ต ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

์ €๋Š” Vue SPA ํ™˜๊ฒฝ์—์„œ๋Š” JWT๋ฅผ, ์ „ํ†ต ์›น์—์„œ๋Š” Session์„ ๊ถŒ์žฅํ•˜์ง€๋งŒ, ์ฆ‰์‹œ ์ฐจ๋‹จ์ด ์ค‘์š”ํ•œ ๊ฒฐ์ œ ๊ฐ™์€ ์˜์—ญ์€ JWT์—ฌ๋„ ๋งŒ๋ฃŒ๋ฅผ ๋งค์šฐ ์งง๊ฒŒ ํ•˜๊ฑฐ๋‚˜ Token Blacklist๋ฅผ ๋‘๋Š” ๋“ฑ ์ถ”๊ฐ€ ์„ค๊ณ„๊ฐ€ ํ•„์š” ํ•˜๋‹ค๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค."

์ž๊ธฐ ์ ๊ฒ€

  • ์œ„ ๋‹ต๋ณ€์„ ๋ณธ์ธ ILIC ์‚ฌ๋ก€๋กœ ์–ด๋–ป๊ฒŒ ๋ณด๊ฐ•ํ•  ์ˆ˜ ์žˆ์„๊นŒ?
  • "Session์ด ๋” ๋‚˜์€ ๊ฒฝ์šฐ๋Š”?" โ€” 5์ดˆ ์•ˆ์— ๋‹ต๋ณ€ ๊ฐ€๋Šฅํ•œ๊ฐ€?

๐Ÿ“š Phase 6 โ€” JWT ์™„์ „ ์ •๋ณต

๋ชฉํ‘œ: JWT์˜ ๊ตฌ์กฐ๋ถ€ํ„ฐ ๋ณด์•ˆ ์ทจ์•ฝ์ ๊นŒ์ง€ ๊นŠ์ด ์žˆ๊ฒŒ ๋งˆ์Šคํ„ฐํ•œ๋‹ค.

Unit 6.1 โ€” JWT ๊ตฌ์กฐ (Header.Payload.Signature) โญ

์„ ์ˆ˜ ์ง€์‹: Phase 5

ํ•ต์‹ฌ ๊ตฌ์กฐ:

eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiI0MiJ9.signature
โ””โ”€โ”€ Header โ”€โ”€โ”˜.โ””โ”€โ”€ Payload โ”€โ”€โ”˜.โ””โ”€โ”€ Signature โ”€โ”€โ”˜

์„ธ ๋ถ€๋ถ„์ด ๋งˆ์นจํ‘œ(.) ๋กœ ๊ตฌ๋ถ„.


1. Header

{
  "alg": "HS256",
  "typ": "JWT"
}

์•Œ๊ณ ๋ฆฌ์ฆ˜ ์ข…๋ฅ˜:

  • HS256 (HMAC-SHA256) โ€” ๋Œ€์นญํ‚ค
  • RS256 (RSA-SHA256) โ€” ๋น„๋Œ€์นญํ‚ค โญ
  • ES256 (ECDSA) โ€” ๋น„๋Œ€์นญํ‚ค, ์ž‘์€ ํฌ๊ธฐ

๋Œ€์นญ vs ๋น„๋Œ€์นญ:

๋Œ€์นญ (HS256)๋น„๋Œ€์นญ (RS256)
ํ‚ค๋‹จ์ผ ๋น„๋ฐ€ํ‚ค๊ณต๊ฐœํ‚ค + ๋น„๋ฐ€ํ‚ค
๋ฐœ๊ธ‰/๊ฒ€์ฆ๊ฐ™์€ ํ‚ค๋น„๋ฐ€ํ‚ค ๋ฐœ๊ธ‰, ๊ณต๊ฐœํ‚ค ๊ฒ€์ฆ
๋ถ„์‚ฐ ํ™˜๊ฒฝํ‚ค ๊ณต์œ  ์–ด๋ ค์›€๊ฒ€์ฆ์ž๊ฐ€ ๊ณต๊ฐœํ‚ค๋งŒ
์ ํ•ฉ๋‹จ์ผ ์„œ๋ฒ„MSA, OAuth2

โ†’ MSA์—์„œ๋Š” RS256 ๊ถŒ์žฅ โญ


2. Payload

{
  "sub": "42",            // Subject โ€” ์‚ฌ์šฉ์ž ID
  "name": "Alice",
  "role": "USER",
  "iat": 1700000000,      // Issued At
  "exp": 1700003600       // Expiration (๋งŒ๋ฃŒ)
}

ํ‘œ์ค€ Claim โญ :

Claim์˜๋ฏธ
iss (Issuer)๋ฐœ๊ธ‰์ž
sub (Subject)์ฃผ์ฒด (๋ณดํ†ต ์‚ฌ์šฉ์ž ID)
aud (Audience)๋Œ€์ƒ
exp (Expiration)๋งŒ๋ฃŒ ์‹œ๊ฐ„ โญ
iat (Issued At)๋ฐœ๊ธ‰ ์‹œ๊ฐ„
nbf (Not Before)์œ ํšจ ์‹œ์ž‘ ์‹œ๊ฐ„
jti (JWT ID)๊ณ ์œ  ์‹๋ณ„์ž

Custom Claim:

  • role, email ๋“ฑ ์ž์œ ๋กญ๊ฒŒ ์ถ”๊ฐ€ ๊ฐ€๋Šฅ

โš ๏ธ ์ฃผ์˜ โญ :

  • Payload๋Š” Base64URL ์ธ์ฝ”๋”ฉ (์•”ํ˜ธํ™” X)
  • ๋ˆ„๊ตฌ๋‚˜ ๋””์ฝ”๋”ฉ ๊ฐ€๋Šฅ โ†’ ๋ฏผ๊ฐ ์ •๋ณด ์ ˆ๋Œ€ X
  • ๋น„๋ฐ€๋ฒˆํ˜ธ, ์‹ ์šฉ์นด๋“œ ๋“ฑ ์ ˆ๋Œ€ X

3. Signature

HMACSHA256(
  base64UrlEncode(header) + "." + base64UrlEncode(payload),
  secret_key
)

์—ญํ•  โญ :

  • ์œ„๋ณ€์กฐ ๋ฐฉ์ง€
  • ํด๋ผ์ด์–ธํŠธ๊ฐ€ Payload ๋ณ€๊ฒฝ ์‹œ๋„ โ†’ Signature ๋ถˆ์ผ์น˜
  • ์„œ๋ฒ„๊ฐ€ ๊ฒ€์ฆ ์‹œ ์ฆ‰์‹œ ๊ฑฐ๋ถ€

ํ•ต์‹ฌ ํ†ต์ฐฐ:

"JWT๋Š” ์•”ํ˜ธํ™” ๊ฐ€ ์•„๋‹Œ ์„œ๋ช… ์ด๋‹ค. ๋ˆ„๊ตฌ๋‚˜ ๋‚ด์šฉ์„ ๋ณผ ์ˆ˜ ์žˆ์ง€๋งŒ, ๋ณ€์กฐํ•˜๋ฉด ๋“คํ‚จ๋‹ค."


๋””์ฝ”๋”ฉ ๋„๊ตฌ:

  • jwt.io โญ โ€” ๋ธŒ๋ผ์šฐ์ €์—์„œ JWT ๋ถ„ํ•ด/๊ฒ€์ฆ
  • ๋””๋ฒ„๊น… ์‹œ ์œ ์šฉ

์ž๊ธฐ ์ ๊ฒ€

  • JWT์˜ Payload๋ฅผ ๋ˆ„๊ตฌ๋‚˜ ์ฝ์„ ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฒƒ์˜ ์˜๋ฏธ๋Š”? (ํžŒํŠธ: ๋ฏผ๊ฐ ์ •๋ณด X)
  • HS256๊ณผ RS256 ์ค‘ MSA์— ์ ํ•ฉํ•œ ์ด์œ ๋Š”? (ํžŒํŠธ: ํ‚ค ๋ถ„๋ฐฐ)

Unit 6.2 โ€” Spring์—์„œ JWT ๊ตฌํ˜„

์„ ์ˆ˜ ์ง€์‹: Unit 6.1

๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ โ€” JJWT (๊ฐ€์žฅ ์ธ๊ธฐ):

implementation 'io.jsonwebtoken:jjwt-api:0.12.5'
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.12.5'
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.12.5'

JwtTokenProvider ๊ตฌํ˜„:

@Component
public class JwtTokenProvider {
    private final SecretKey secretKey;
    private final long accessTokenValidity = 1000 * 60 * 60;  // 1์‹œ๊ฐ„
    private final long refreshTokenValidity = 1000 * 60 * 60 * 24 * 7;  // 7์ผ
    
    public JwtTokenProvider(@Value("${jwt.secret}") String secret) {
        this.secretKey = Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8));
    }
    
    // ํ† ํฐ ์ƒ์„ฑ
    public String createAccessToken(Authentication auth) {
        UserDetails userDetails = (UserDetails) auth.getPrincipal();
        String authorities = userDetails.getAuthorities().stream()
            .map(GrantedAuthority::getAuthority)
            .collect(Collectors.joining(","));
        
        return Jwts.builder()
            .subject(userDetails.getUsername())
            .claim("authorities", authorities)
            .issuedAt(new Date())
            .expiration(new Date(System.currentTimeMillis() + accessTokenValidity))
            .signWith(secretKey, Jwts.SIG.HS256)
            .compact();
    }
    
    // ํ† ํฐ ๊ฒ€์ฆ + Authentication ๋ฐ˜ํ™˜
    public Authentication getAuthentication(String token) {
        Claims claims = Jwts.parser()
            .verifyWith(secretKey)
            .build()
            .parseSignedClaims(token)
            .getPayload();
        
        Collection<? extends GrantedAuthority> authorities =
            Arrays.stream(claims.get("authorities").toString().split(","))
                .map(SimpleGrantedAuthority::new)
                .toList();
        
        UserDetails principal = new User(claims.getSubject(), "", authorities);
        return new UsernamePasswordAuthenticationToken(principal, token, authorities);
    }
    
    // ํ† ํฐ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ
    public boolean validateToken(String token) {
        try {
            Jwts.parser().verifyWith(secretKey).build().parseSignedClaims(token);
            return true;
        } catch (ExpiredJwtException e) {
            log.info("Expired JWT");
        } catch (JwtException | IllegalArgumentException e) {
            log.error("Invalid JWT");
        }
        return false;
    }
}

JwtAuthenticationFilter (์ปค์Šคํ…€ Filter):

@Component
@RequiredArgsConstructor
public class JwtAuthenticationFilter extends OncePerRequestFilter {
    private final JwtTokenProvider jwtProvider;
    
    @Override
    protected void doFilterInternal(
        HttpServletRequest request, 
        HttpServletResponse response, 
        FilterChain filterChain
    ) throws ServletException, IOException {
        String token = resolveToken(request);
        
        if (token != null && jwtProvider.validateToken(token)) {
            Authentication auth = jwtProvider.getAuthentication(token);
            SecurityContextHolder.getContext().setAuthentication(auth);
        }
        
        filterChain.doFilter(request, response);
    }
    
    private String resolveToken(HttpServletRequest request) {
        String bearer = request.getHeader("Authorization");
        if (StringUtils.hasText(bearer) && bearer.startsWith("Bearer ")) {
            return bearer.substring(7);
        }
        return null;
    }
}

SecurityConfig ํ†ตํ•ฉ:

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http
        .csrf(AbstractHttpConfigurer::disable)
        .sessionManagement(s -> s.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
        .authorizeHttpRequests(auth -> auth
            .requestMatchers("/api/auth/**").permitAll()
            .anyRequest().authenticated()
        )
        .addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class);
    return http.build();
}

ํ•ต์‹ฌ:

  • STATELESS โ€” ์„ธ์…˜ ์ƒ์„ฑ X
  • csrf().disable() โ€” JWT๋Š” CSRF ์œ„ํ—˜ ๋‚ฎ์Œ
  • ์ปค์Šคํ…€ Filter๋ฅผ UsernamePasswordAuthenticationFilter ์•ž์—

์ž๊ธฐ ์ ๊ฒ€

  • ์™œ OncePerRequestFilter๋ฅผ ์ƒ์†? (ํžŒํŠธ: ํ•œ ์š”์ฒญ๋‹น ํ•œ ๋ฒˆ๋งŒ โ€” Filter ์ค‘๋ณต ํ˜ธ์ถœ ๋ฐฉ์ง€)
  • JWT๋ฅผ ๊ฒ€์ฆํ•  ๋•Œ DB ์กฐํšŒ๊ฐ€ ํ•„์š”ํ•œ๊ฐ€? (ํžŒํŠธ: ๋ณดํ†ต NO โ€” ์„œ๋ช…๋งŒ)

Unit 6.3 โ€” Refresh Token ์ „๋žต โญ

์„ ์ˆ˜ ์ง€์‹: Unit 6.2

๋ฌธ์ œ:

  • Access Token์„ ๊ธธ๊ฒŒ โ†’ ์ฆ‰์‹œ ํ๊ธฐ ์–ด๋ ค์›€ + ๋ณด์•ˆ โ†“
  • Access Token์„ ์งง๊ฒŒ โ†’ ์‚ฌ์šฉ์ž ๋งค๋ฒˆ ๋กœ๊ทธ์ธ = UX โ†“

ํ•ด๊ฒฐ โ€” Access + Refresh ํŒจํ„ด โญ :

[Login]
   โ†“
Access Token (15๋ถ„) + Refresh Token (7์ผ)
   โ†“
Access ๋งŒ๋ฃŒ ์‹œ:
   โ†“
Refresh Token์œผ๋กœ ์ƒˆ Access Token ๋ฐœ๊ธ‰
   โ†“
Refresh ๋งŒ๋ฃŒ ์‹œ: ์žฌ๋กœ๊ทธ์ธ

๊ตฌํ˜„:

1. ๋กœ๊ทธ์ธ ์‹œ ๋‘˜ ๋‹ค ๋ฐœ๊ธ‰:

@PostMapping("/api/auth/login")
public LoginResponse login(@RequestBody LoginRequest request) {
    Authentication auth = authManager.authenticate(...);
    
    String accessToken = jwtProvider.createAccessToken(auth);
    String refreshToken = jwtProvider.createRefreshToken(auth);
    
    // Refresh Token์„ Redis์— ์ €์žฅ (์ฆ‰์‹œ ํ๊ธฐ ๊ฐ€๋Šฅ)
    refreshTokenRepository.save(
        new RefreshToken(auth.getName(), refreshToken, Duration.ofDays(7))
    );
    
    return new LoginResponse(accessToken, refreshToken);
}

2. Access ๋งŒ๋ฃŒ ์‹œ ๊ฐฑ์‹ :

@PostMapping("/api/auth/refresh")
public AccessTokenResponse refresh(@RequestBody RefreshRequest request) {
    String refreshToken = request.getRefreshToken();
    
    if (!jwtProvider.validateToken(refreshToken)) {
        throw new InvalidTokenException();
    }
    
    String username = jwtProvider.getUsername(refreshToken);
    
    // Redis ๊ฒ€์ฆ (ํƒˆ์ทจ๋œ ํ† ํฐ ๋ฐฉ์–ด)
    if (!refreshTokenRepository.existsByUsernameAndToken(username, refreshToken)) {
        throw new InvalidTokenException();
    }
    
    // ์ƒˆ Access Token ๋ฐœ๊ธ‰
    Authentication auth = jwtProvider.getAuthentication(refreshToken);
    return new AccessTokenResponse(jwtProvider.createAccessToken(auth));
}

Refresh Token Rotation (๋ณด์•ˆ ๊ฐ•ํ™”) โญ :

  • Refresh ์‚ฌ์šฉ ์‹œ๋งˆ๋‹ค ์ƒˆ Refresh๋„ ๋ฐœ๊ธ‰
  • ์˜› Refresh๋Š” ์ฆ‰์‹œ ๋ฌดํšจ
  • ํƒˆ์ทจ ๊ฐ์ง€ ๊ฐ€๋Šฅ
// refresh ์‹œ
String newRefresh = jwtProvider.createRefreshToken(auth);
refreshTokenRepository.delete(oldRefresh);
refreshTokenRepository.save(newRefresh);

์ €์žฅ ์œ„์น˜ ๊ฒฐ์ • โš ๏ธ :

LocalStorage:

  • ๊ฐ„๋‹จ
  • XSS ์ทจ์•ฝ (JS์—์„œ ์ ‘๊ทผ ๊ฐ€๋Šฅ)

HttpOnly Cookie:

  • XSS ์•ˆ์ „
  • CSRF ์œ„ํ—˜ (SameSite๋กœ ์™„ํ™”)
  • ๊ถŒ์žฅ โญ

Memory (๋ณ€์ˆ˜):

  • ๊ฐ€์žฅ ์•ˆ์ „
  • ํŽ˜์ด์ง€ ์ƒˆ๋กœ๊ณ ์นจ ์‹œ ์†์‹ค โ†’ ๋งค๋ฒˆ ์žฌ๋กœ๊ทธ์ธ

์‹ค๋ฌด ํŒจํ„ด:

  • Access Token โ†’ Memory
  • Refresh Token โ†’ HttpOnly Cookie

ILIC ๊ถŒ์žฅ:

  • Access (15๋ถ„) + Refresh (7์ผ)
  • Refresh Token Rotation ์ ์šฉ
  • HttpOnly Cookie ์ €์žฅ

์ž๊ธฐ ์ ๊ฒ€

  • ์™œ Refresh Token๋„ ๊ฒ€์ฆ์„ DB/Redis์—์„œ ํ•˜๋Š”๊ฐ€? (ํžŒํŠธ: ์ฆ‰์‹œ ํ๊ธฐ ๊ฐ€๋Šฅ์„ฑ)
  • "Access Token์ด ํƒˆ์ทจ๋˜๋ฉด?"์˜ ๋‹ต๋ณ€์€? (ํžŒํŠธ: ๋งŒ๋ฃŒ๊ฐ€ ์งง์•„ ํ”ผํ•ด ์ œํ•œ์ )

Unit 6.4 โ€” JWT ๋ณด์•ˆ ์ทจ์•ฝ์ ๊ณผ ๋ฐฉ์–ด โš ๏ธ (๋ฉด์ ‘ ํ•ต์‹ฌ)

์„ ์ˆ˜ ์ง€์‹: Unit 6.1~6.3

์ฃผ์š” ์ทจ์•ฝ์  โญ :

1. None Algorithm ๊ณต๊ฒฉ

  • ๊ณต๊ฒฉ์ž๊ฐ€ Header๋ฅผ "alg": "none" ๋กœ ๋ณ€์กฐ
  • ์„œ๋ช… ์—†์ด ๊ฒ€์ฆ ํ†ต๊ณผ ์‹œ๋„

๋ฐฉ์–ด:

// JJWT๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ none ์ฐจ๋‹จ (์ž๋™)
// ๊ทธ๋Ÿฌ๋‚˜ ๋ช…์‹œ ๊ถŒ์žฅ
.parser()
.verifyWith(secretKey)
.requireIssuer("ilic")
.build()

2. ์•Œ๊ณ ๋ฆฌ์ฆ˜ ํ˜ผ๋™ ๊ณต๊ฒฉ (Algorithm Confusion)

  • RS256์œผ๋กœ ๋ฐœ๊ธ‰๋œ ํ† ํฐ์„ HS256์œผ๋กœ ๊ฒ€์ฆ ์‹œ๋„
  • ๊ณต๊ฐœํ‚ค๋ฅผ ๋น„๋ฐ€ํ‚ค๋กœ ์˜ค์ธ

๋ฐฉ์–ด:

  • ๊ฒ€์ฆ ์‹œ ์•Œ๊ณ ๋ฆฌ์ฆ˜ ๋ช…์‹œ
  • ๊ณต๊ฐœํ‚ค/๋น„๋ฐ€ํ‚ค ๋ถ„๋ฆฌ ๊ด€๋ฆฌ

3. ์•ฝํ•œ ๋น„๋ฐ€ํ‚ค

  • "mysecret" ๊ฐ™์€ ์งง์€ ํ‚ค
  • ๋ฌด์ฐจ๋ณ„ ๋Œ€์ž… ๊ณต๊ฒฉ ๊ฐ€๋Šฅ

๋ฐฉ์–ด:

  • 256๋น„ํŠธ(32๋ฐ”์ดํŠธ) ์ด์ƒ ๊ถŒ์žฅ
  • ํ™˜๊ฒฝ๋ณ€์ˆ˜, Secret Manager ์‚ฌ์šฉ
// ์•ˆ์ „ํ•œ ํ‚ค ์ƒ์„ฑ
SecretKey key = Jwts.SIG.HS256.key().build();
String secretString = Encoders.BASE64.encode(key.getEncoded());

4. JWT ํƒˆ์ทจ

  • LocalStorage์— ์ €์žฅ ์‹œ XSS ์œ„ํ—˜
  • ๋„คํŠธ์›Œํฌ ๋„์ฒญ

๋ฐฉ์–ด:

  • HttpOnly Cookie + Secure + SameSite
  • HTTPS ํ•„์ˆ˜
  • ์งง์€ ๋งŒ๋ฃŒ ์‹œ๊ฐ„

5. ๋งŒ๋ฃŒ๋œ ํ† ํฐ ์‚ฌ์šฉ

  • ์„œ๋ฒ„ ์‹œ๊ฐ„ ๋™๊ธฐํ™” ๋ฌธ์ œ
  • Clock Skew

๋ฐฉ์–ด:

.parser()
.clockSkewSeconds(60)  // 60์ดˆ ํ—ˆ์šฉ
.build()

6. ๋ฏผ๊ฐ ์ •๋ณด Payload ๋…ธ์ถœ

  • ๋น„๋ฐ€๋ฒˆํ˜ธ, ์‹ ์šฉ์นด๋“œ ๋“ฑ์„ Payload์—

๋ฐฉ์–ด:

  • Payload์—๋Š” ์‹๋ณ„์ž๋งŒ
  • ๋ฏผ๊ฐ ์ •๋ณด๋Š” ์„œ๋ฒ„์—์„œ ์กฐํšŒ

๋ณด์•ˆ ์ฒดํฌ๋ฆฌ์ŠคํŠธ โญ :

  • HS256์€ 32๋ฐ”์ดํŠธ ์ด์ƒ ํ‚ค
  • MSA์—์„œ๋Š” RS256
  • HTTPS ํ•„์ˆ˜
  • HttpOnly Cookie ๋˜๋Š” Memory ์ €์žฅ
  • Access Token ์งง๊ฒŒ (15๋ถ„~1์‹œ๊ฐ„)
  • Refresh Token Rotation
  • ๊ฒ€์ฆ ์‹œ ์•Œ๊ณ ๋ฆฌ์ฆ˜ ๋ช…์‹œ
  • ๋งŒ๋ฃŒ ์‹œ๊ฐ„(exp) ํ•ญ์ƒ ํฌํ•จ
  • Payload์— ๋ฏผ๊ฐ ์ •๋ณด X
  • ๋น„๋ฐ€ํ‚ค๋Š” ํ™˜๊ฒฝ๋ณ€์ˆ˜/Secret Manager

์ž๊ธฐ ์ ๊ฒ€

  • "JWT์˜ ๊ฐ€์žฅ ํฐ ๋ณด์•ˆ ์œ„ํ—˜์€?" (ํžŒํŠธ: ์ฆ‰์‹œ ํ๊ธฐ ์–ด๋ ค์›€ + ํƒˆ์ทจ ์‹œ ๋งŒ๋ฃŒ ์ „๊นŒ์ง€ ์œ ํšจ)
  • ILIC์—์„œ JWT ์‚ฌ์šฉ ์‹œ ๊ฒ€์ฆํ•ด์•ผ ํ•  ๋ณด์•ˆ ์‚ฌํ•ญ์„ 5๊ฐ€์ง€ ๋‚˜์—ดํ•˜๋ผ

๐Ÿ“š Phase 7 โ€” OAuth2์™€ OpenID Connect

๋ชฉํ‘œ: ํ˜„๋Œ€ ์ธ์ฆ์˜ ํ‘œ์ค€์ธ OAuth2์˜ ํ๋ฆ„์„ ์ดํ•ดํ•œ๋‹ค.

Unit 7.1 โ€” OAuth2 ๋“ฑ์žฅ ๋ฐฐ๊ฒฝ๊ณผ ์—ญํ• 

์„ ์ˆ˜ ์ง€์‹: Phase 6

๋ฌธ์ œ:

"์‚ฌ์šฉ์ž๊ฐ€ ๋งค๋ฒˆ ID/PW ์ž…๋ ฅํ•˜์ง€ ์•Š๊ณ , ๋‹ค๋ฅธ ์„œ๋น„์Šค์˜ ์ธ์ฆ์„ ํ™œ์šฉ ํ•˜๊ณ  ์‹ถ๋‹ค"

์˜ˆ: "Google๋กœ ๋กœ๊ทธ์ธ", "GitHub์œผ๋กœ ๋กœ๊ทธ์ธ"

์ „ํ†ต ๋ฐฉ์‹์˜ ์œ„ํ—˜:

  • A ์„œ๋น„์Šค๊ฐ€ B ์„œ๋น„์Šค์˜ ID/PW๋ฅผ ๋ฐ›์Œ โ†’ A๊ฐ€ B๋ฅผ ์กฐ์ž‘ ๊ฐ€๋Šฅ
  • A๊ฐ€ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์ €์žฅ โ†’ ํƒˆ์ทจ ์‹œ ๋ชจ๋“  ์„œ๋น„์Šค ์œ„ํ—˜

OAuth2์˜ ํ•ด๊ฒฐ:

"๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ๊ณต์œ ํ•˜์ง€ ์•Š๊ณ  ๊ถŒํ•œ๋งŒ ์œ„์ž„"

๋น„์œ :

ํ˜ธํ…” ๋ฐœ๋ › ํ‚ค โ€” ์‹œ๋™๋งŒ ๊ฑธ ์ˆ˜ ์žˆ๊ณ  ํŠธ๋ ํฌ๋Š” ๋ชป ์—ถ.


4๊ฐ€์ง€ ์—ญํ•  โญ :

์—ญํ• ์˜๋ฏธ
Resource Owner์‚ฌ์šฉ์ž (๋ณธ์ธ)
Client์‚ฌ์šฉํ•˜๋ ค๋Š” ์•ฑ (์˜ˆ: Spotify)
Authorization Server๊ถŒํ•œ ๋ฐœ๊ธ‰ (์˜ˆ: Google)
Resource Server๋ณดํ˜ธ๋œ ์ž์› (์˜ˆ: Google Drive API)

์‹œ๋‚˜๋ฆฌ์˜ค โ€” Spotify์—์„œ Google ๋กœ๊ทธ์ธ:

  1. Resource Owner (์‚ฌ์šฉ์ž): Spotify ์•ฑ ์‚ฌ์šฉ
  2. Client (Spotify): "Google๋กœ ๋กœ๊ทธ์ธ" ๋ฒ„ํŠผ
  3. Authorization Server (Google): ์‚ฌ์šฉ์ž ์ธ์ฆ + ๋™์˜ ํ™”๋ฉด
  4. Resource Server (Google API): Spotify์— ์‚ฌ์šฉ์ž ์ •๋ณด ์ œ๊ณต

์ž๊ธฐ ์ ๊ฒ€

  • 4๊ฐ€์ง€ ์—ญํ•  ์ค‘ ILIC๊ฐ€ Authorization Server๊ฐ€ ๋  ์ˆ˜ ์žˆ๋Š”๊ฐ€? (ํžŒํŠธ: ๊ฐ€๋Šฅ โ€” ์ž์ฒด OAuth2 ๋ฐœ๊ธ‰)
  • Resource Server์™€ Authorization Server๋ฅผ ๋ถ„๋ฆฌํ•˜๋Š” ์ด์œ ๋Š”? (ํžŒํŠธ: ์ฑ…์ž„ ๋ถ„๋ฆฌ, ํ™•์žฅ์„ฑ)

Unit 7.2 โ€” Authorization Code Grant ํ๋ฆ„ โญ

์„ ์ˆ˜ ์ง€์‹: Unit 7.1

๊ฐ€์žฅ ํ”ํ•œ OAuth2 ํ๋ฆ„ (์„œ๋ฒ„ ์•ฑ):

1. [User] Spotify์—์„œ "Google๋กœ ๋กœ๊ทธ์ธ" ํด๋ฆญ
       โ†“
2. [Spotify] ์‚ฌ์šฉ์ž๋ฅผ Google ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€๋กœ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ
   GET https://accounts.google.com/o/oauth2/auth?
       client_id=spotify_client_id&
       redirect_uri=https://spotify.com/callback&
       response_type=code&
       scope=email profile
       โ†“
3. [User] Google์— ๋กœ๊ทธ์ธ + ๋™์˜ ํ™”๋ฉด
       โ†“
4. [Google] Spotify๋กœ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ + Authorization Code
   GET https://spotify.com/callback?code=AUTH_CODE
       โ†“
5. [Spotify Server] Code๋กœ Access Token ์š”์ฒญ
   POST https://oauth2.googleapis.com/token
   { code, client_id, client_secret, redirect_uri, grant_type }
       โ†“
6. [Google] Access Token + (Refresh Token) ์‘๋‹ต
       โ†“
7. [Spotify] Access Token์œผ๋กœ Google API ํ˜ธ์ถœ
   GET https://googleapis.com/userinfo
   Authorization: Bearer ACCESS_TOKEN
       โ†“
8. [Google] ์‚ฌ์šฉ์ž ์ •๋ณด ๋ฐ˜ํ™˜
       โ†“
9. [Spotify] ์‚ฌ์šฉ์ž ๋“ฑ๋ก/๋กœ๊ทธ์ธ ์™„๋ฃŒ

ํ•ต์‹ฌ:

  • Code๋Š” ํ•œ ๋ฒˆ๋งŒ ์‚ฌ์šฉ ๊ฐ€๋Šฅ
  • Code โ†’ Token ๊ตํ™˜์€ ์„œ๋ฒ„ ๊ฐ„ (client_secret ๋ณดํ˜ธ)
  • Token์€ ํด๋ผ์ด์–ธํŠธ์— ๋…ธ์ถœ X

๋‹ค๋ฅธ Grant Type:

Grant์šฉ๋„
Authorization Code โญ์ผ๋ฐ˜ ์›น ์•ฑ
Authorization Code + PKCESPA, ๋ชจ๋ฐ”์ผ
Client Credentials์„œ๋ฒ„ ๊ฐ„ (์‚ฌ์šฉ์ž X)
Resource Owner Password๋ ˆ๊ฑฐ์‹œ (๊ถŒ์žฅ X)
Implicit์˜›๋‚  ๋ฐฉ์‹ (๊ถŒ์žฅ X)

PKCE (Proof Key for Code Exchange):

  • SPA/๋ชจ๋ฐ”์ผ์—์„œ client_secret ์ €์žฅ ์œ„ํ—˜
  • โ†’ ๋™์ ์œผ๋กœ ์ƒ์„ฑํ•œ verifier๋กœ ๋Œ€์ฒด
  • ํ˜„๋Œ€ ํ‘œ์ค€ โญ

์ž๊ธฐ ์ ๊ฒ€

  • Authorization Code๋ฅผ ์ง์ ‘ ๋ฐ›์ง€ ์•Š๊ณ  Token์œผ๋กœ ๊ตํ™˜ํ•˜๋Š” ์ด์œ ๋Š”? (ํžŒํŠธ: ๋ณด์•ˆ โ€” ๋ธŒ๋ผ์šฐ์ € ๋…ธ์ถœ ๋ฐฉ์ง€)
  • ILIC์—์„œ Google/Naver ๋กœ๊ทธ์ธ์„ ์ถ”๊ฐ€ํ•œ๋‹ค๋ฉด ์–ด๋–ค Grant? (ํžŒํŠธ: Authorization Code)

Unit 7.3 โ€” OpenID Connect (OIDC)

์„ ์ˆ˜ ์ง€์‹: Unit 7.2

๋ฌธ์ œ:

  • OAuth2๋Š” ์ธ๊ฐ€(๊ถŒํ•œ ์œ„์ž„) ํ‘œ์ค€
  • "์ธ์ฆ(๋ˆ„๊ตฌ์ธ์ง€)"๋Š” ๋ช…์‹œ๋˜์ง€ ์•Š์Œ
  • ๊ฐ ์„œ๋น„์Šค๊ฐ€ ๋‹ค๋ฅด๊ฒŒ ๊ตฌํ˜„ โ†’ ํ‘œ์ค€ํ™” ์–ด๋ ค์›€

OIDC (OpenID Connect):

"OAuth2 ์œ„์— ์ธ์ฆ ๋ ˆ์ด์–ด๋ฅผ ํ‘œ์ค€ํ™”"

ํ•ต์‹ฌ ์ถ”๊ฐ€:

  • ID Token (JWT) ๋ฐœ๊ธ‰
  • ํ‘œ์ค€ ์‚ฌ์šฉ์ž ์ •๋ณด ์—”๋“œํฌ์ธํŠธ (/userinfo)
  • ํ‘œ์ค€ Claim (sub, email, name ๋“ฑ)

ID Token vs Access Token:

ID TokenAccess Token
์šฉ๋„์‚ฌ์šฉ์ž ์ •๋ณดAPI ํ˜ธ์ถœ ๊ถŒํ•œ
ํ˜•์‹ํ•ญ์ƒ JWTJWT ๋˜๋Š” ๋ถˆํˆฌ๋ช… ๋ฌธ์ž์—ด
๊ฒ€์ฆClient๊ฐ€ ๊ฒ€์ฆResource Server๊ฐ€ ๊ฒ€์ฆ
๋‚ด์šฉ์‚ฌ์šฉ์ž ์‹๋ณ„๊ถŒํ•œ
// ID Token Payload ์˜ˆ
{
  "iss": "https://accounts.google.com",
  "sub": "10769150350006150715113082367",
  "email": "alice@example.com",
  "name": "Alice",
  "picture": "https://...",
  "iat": 1700000000,
  "exp": 1700003600
}

Spring Security OAuth2 Client:

implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'
spring:
  security:
    oauth2:
      client:
        registration:
          google:
            client-id: ${GOOGLE_CLIENT_ID}
            client-secret: ${GOOGLE_CLIENT_SECRET}
            scope: email,profile
          github:
            client-id: ${GITHUB_CLIENT_ID}
            client-secret: ${GITHUB_CLIENT_SECRET}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http
        .oauth2Login(oauth -> oauth
            .defaultSuccessUrl("/home")
            .userInfoEndpoint(userInfo -> userInfo
                .userService(customOAuth2UserService)  // ์‚ฌ์šฉ์ž ์ฒ˜๋ฆฌ ์ปค์Šคํ…€
            )
        );
    return http.build();
}

โ†’ ์ž๋™์œผ๋กœ OAuth2/OIDC ํ๋ฆ„ ์ฒ˜๋ฆฌ

ILIC ์‹œ๋‚˜๋ฆฌ์˜ค:

  • "Google ๋กœ๊ทธ์ธ" ์ถ”๊ฐ€ โ†’ OAuth2 Client
  • "ILIC ๊ณ„์ •์œผ๋กœ ๋‹ค๋ฅธ ์„œ๋น„์Šค ๋กœ๊ทธ์ธ" โ†’ OAuth2 Server (๋ณต์žก, ๋ณดํ†ต Keycloak ๊ฐ™์€ ์†”๋ฃจ์…˜)

์ž๊ธฐ ์ ๊ฒ€

  • OAuth2์™€ OIDC์˜ ์ฐจ์ด๋ฅผ ํ•œ ๋ฌธ์žฅ์œผ๋กœ?
  • ILIC๊ฐ€ ์ž์ฒด OAuth2 Server๋ฅผ ๋งŒ๋“œ๋Š” ๊ฒŒ ์ข‹์€๊ฐ€? (ํžŒํŠธ: ๋ณดํ†ต NO โ€” Keycloak/Auth0 ์‚ฌ์šฉ)

๐Ÿ“š Phase 8 โ€” ๋ณด์•ˆ ์ทจ์•ฝ์ ๊ณผ ๋ฐฉ์–ด

๋ชฉํ‘œ: ์›น ๋ณด์•ˆ์˜ 3๋Œ€ ์œ„ํ˜‘ โ€” CSRF, XSS, CORS โ€” ๋ฅผ ๊นŠ์ด ์ดํ•ดํ•œ๋‹ค.

Unit 8.1 โ€” CSRF (Cross-Site Request Forgery) โญ

์„ ์ˆ˜ ์ง€์‹: Phase 5

ํ•ต์‹ฌ ๊ฐœ๋…

CSRF:

"๋‹ค๋ฅธ ์‚ฌ์ดํŠธ์—์„œ ์‚ฌ์šฉ์ž์˜ ์ธ์ฆ ์ •๋ณด๋ฅผ ํ™œ์šฉํ•ด ์›์น˜ ์•Š๋Š” ์š”์ฒญ์„ ๋ณด๋‚ด๊ฒŒ ํ•จ"

์‹œ๋‚˜๋ฆฌ์˜ค:

1. ์‚ฌ์šฉ์ž๊ฐ€ Bank.com์— ๋กœ๊ทธ์ธ โ†’ Cookie ์ €์žฅ
2. ์‚ฌ์šฉ์ž๊ฐ€ Evil.com ๋ฐฉ๋ฌธ
3. Evil.com์— ์ˆจ๊ฒจ์ง„ ํผ:
   <form action="https://bank.com/transfer" method="POST">
     <input name="to" value="hacker">
     <input name="amount" value="1000000">
   </form>
4. ์ž๋™ ์ œ์ถœ โ†’ Bank.com์— ์š”์ฒญ
5. Bank.com: "์‚ฌ์šฉ์ž๊ฐ€ ๋กœ๊ทธ์ธ๋˜์–ด ์žˆ๋„ค" โ†’ ์†ก๊ธˆ ์ฒ˜๋ฆฌ!

ํ•ต์‹ฌ:

  • Cookie๋Š” ๋„๋ฉ”์ธ ๊ธฐ๋ฐ˜ ์ž๋™ ์ „์†ก
  • ์‚ฌ์šฉ์ž๊ฐ€ ๋ชจ๋ฅด๋Š” ์‚ฌ์ด ์š”์ฒญ ๋ฐœ์ƒ

๋ฐฉ์–ด ๋ฐฉ๋ฒ• โญ :

1. CSRF Token

  • ํผ๋งˆ๋‹ค ๋žœ๋ค ํ† ํฐ ๋ฐœ๊ธ‰
  • ์„œ๋ฒ„๊ฐ€ ํ† ํฐ ๊ฒ€์ฆ โ†’ ์™ธ๋ถ€ ์‚ฌ์ดํŠธ๋Š” ํ† ํฐ ๋ชจ๋ฆ„
<form>
  <input type="hidden" name="_csrf" value="abc123">
  <!-- ... -->
</form>

Spring Security ๊ธฐ๋ณธ ํ™œ์„ฑํ™”:

http.csrf(csrf -> csrf
    .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
);
Set-Cookie: SESSIONID=abc; SameSite=Lax
๊ฐ’์˜๋ฏธ
Strict๊ฐ™์€ ์‚ฌ์ดํŠธ๋งŒ ์ „์†ก (๊ฐ€์žฅ ์•ˆ์ „)
LaxGET ๋“ฑ ์•ˆ์ „ ๋ฉ”์„œ๋“œ๋งŒ cross-site (๊ธฐ๋ณธ)
None๋ชจ๋“  cross-site (Secure ํ•„์ˆ˜)

3. JWT (Authorization ํ—ค๋”)

  • Cookie ์ž๋™ ์ „์†ก X
  • JS์—์„œ ๋ช…์‹œ์ ์œผ๋กœ ํ—ค๋” ์„ค์ •
  • โ†’ CSRF ์œ„ํ—˜ ์ž๋™ ํšŒํ”ผ

JWT ์‚ฌ์šฉ ์‹œ:

http.csrf(AbstractHttpConfigurer::disable);
// JWT๋Š” CSRF ์œ„ํ—˜ ๋‚ฎ์Œ

์–ธ์ œ CSRF ํ™œ์„ฑํ™”/๋น„ํ™œ์„ฑํ™”?:

  • Session + Cookie โ†’ ํ™œ์„ฑํ™” โญ
  • JWT (Authorization ํ—ค๋”) โ†’ ๋น„ํ™œ์„ฑํ™” OK
  • API ์„œ๋ฒ„ (Stateless) โ†’ ๋น„ํ™œ์„ฑํ™” OK

์ž๊ธฐ ์ ๊ฒ€

  • "JWT๋ฅผ HttpOnly Cookie์— ์ €์žฅํ•˜๋ฉด CSRF ์œ„ํ—˜์€?" (ํžŒํŠธ: ์ž๋™ ์ „์†ก โ†’ ์œ„ํ—˜ โ€” Token ํ—ค๋” ๋ฐฉ์‹์ด ์•ˆ์ „)
  • SameSite=Strict์˜ ๋‹จ์ ์€? (ํžŒํŠธ: ์™ธ๋ถ€ ๋งํฌ์—์„œ ๋กœ๊ทธ์ธ ์•ˆ ๋จ)

Unit 8.2 โ€” XSS (Cross-Site Scripting)

์„ ์ˆ˜ ์ง€์‹: Unit 8.1

ํ•ต์‹ฌ ๊ฐœ๋…

XSS:

"์•…์˜์  JavaScript ๋ฅผ ๋‹ค๋ฅธ ์‚ฌ์šฉ์ž์˜ ๋ธŒ๋ผ์šฐ์ €์—์„œ ์‹คํ–‰"


3๊ฐ€์ง€ ์œ ํ˜•:

1. Stored XSS (์ €์žฅํ˜•) โ€” ๊ฐ€์žฅ ์œ„ํ—˜

1. ๊ณต๊ฒฉ์ž: ๊ฒŒ์‹œ๊ธ€์— <script>fetch('/api/cookies?c=' + document.cookie)</script>
2. DB ์ €์žฅ
3. ๋‹ค๋ฅธ ์‚ฌ์šฉ์ž๊ฐ€ ๊ฒŒ์‹œ๊ธ€ ์กฐํšŒ โ†’ ์Šคํฌ๋ฆฝํŠธ ์‹คํ–‰
4. ์‚ฌ์šฉ์ž์˜ Cookie๋ฅผ ๊ณต๊ฒฉ์ž ์„œ๋ฒ„๋กœ ์ „์†ก

2. Reflected XSS (๋ฐ˜์‚ฌํ˜•)

URL: /search?q=<script>...</script>
์„œ๋ฒ„๊ฐ€ ๊ฒ€์ƒ‰์–ด๋ฅผ ๊ทธ๋Œ€๋กœ ํŽ˜์ด์ง€์— ๋…ธ์ถœ โ†’ ์‹คํ–‰

3. DOM-based XSS

  • JS๊ฐ€ URL ๋“ฑ์—์„œ ๊ฐ’์„ ๋ฐ›์•„ DOM์— ์‚ฝ์ž…
  • ์„œ๋ฒ„ ๊ฑฐ์น˜์ง€ ์•Š์Œ

๋ฐฉ์–ด ๋ฐฉ๋ฒ• โญ :

1. ์ถœ๋ ฅ ์ด์Šค์ผ€์ดํ”„ (๊ฐ€์žฅ ์ค‘์š”)

Thymeleaf (์ž๋™ ์ด์Šค์ผ€์ดํ”„):

<p th:text="${userInput}">  <!-- ์ž๋™ ์ด์Šค์ผ€์ดํ”„ โœ… -->
<p th:utext="${userInput}"> <!-- ์ด์Šค์ผ€์ดํ”„ X โš ๏ธ -->

JSP:

<c:out value="${userInput}"/>  <!-- ์ž๋™ ์ด์Šค์ผ€์ดํ”„ -->

React/Vue: ๊ธฐ๋ณธ ์ด์Šค์ผ€์ดํ”„ ์ ์šฉ ({userInput} vs dangerouslySetInnerHTML)

2. Input Validation

@PostMapping("/comment")
public Comment create(@Valid @RequestBody CommentRequest request) {
    // <, >, " ๋“ฑ ์ฐจ๋‹จ ๋˜๋Š” ์ธ์ฝ”๋”ฉ
}

3. Content Security Policy (CSP)

Content-Security-Policy: default-src 'self'; script-src 'self'

โ†’ ์ธ๋ผ์ธ ์Šคํฌ๋ฆฝํŠธ ์ฐจ๋‹จ

  • JS์—์„œ Cookie ์ ‘๊ทผ X
  • XSS๋กœ Cookie ํƒˆ์ทจ ๋ถˆ๊ฐ€
Set-Cookie: SESSIONID=abc; HttpOnly

JWT ์ €์žฅ๊ณผ XSS โš ๏ธ :

  • LocalStorage: XSS ์‹œ ์ง์ ‘ ํƒˆ์ทจ ๊ฐ€๋Šฅ
  • HttpOnly Cookie: XSS๋กœ๋„ ์ ‘๊ทผ X (์•ˆ์ „)
  • โ†’ JWT๋Š” HttpOnly Cookie ๊ถŒ์žฅ โญ

์ž๊ธฐ ์ ๊ฒ€

  • Stored XSS์™€ Reflected XSS์˜ ์ฐจ์ด๋Š”?
  • ILIC๊ฐ€ ์‚ฌ์šฉ์ž ์ž…๋ ฅ์„ ๋ฐ›๋Š” ๋ชจ๋“  ๊ณณ์—์„œ ๊ฒ€์ฆํ•ด์•ผ ํ•  ์‚ฌํ•ญ์€?

Unit 8.3 โ€” CORS (Cross-Origin Resource Sharing) โญ

์„ ์ˆ˜ ์ง€์‹: Phase 1

ํ•ต์‹ฌ ๊ฐœ๋…

Same-Origin Policy (๋ธŒ๋ผ์šฐ์ € ๋ณด์•ˆ ๊ธฐ๋ณธ):

"JS๋Š” ๊ฐ™์€ origin(scheme + host + port) ์˜ ๋ฆฌ์†Œ์Šค๋งŒ ์ ‘๊ทผ ๊ฐ€๋Šฅ"

์˜ˆ:

  • https://ilic.com:443 ์˜ JS โ†’ https://ilic.com:443/api โœ…
  • https://ilic.com:443 ์˜ JS โ†’ https://api.ilic.com:443 โŒ (๋‹ค๋ฅธ host)
  • https://ilic.com:443 ์˜ JS โ†’ http://ilic.com:443 โŒ (๋‹ค๋ฅธ scheme)

์™œ ์ด ์ •์ฑ…?:

  • ์•…์˜์  ์‚ฌ์ดํŠธ๊ฐ€ ๋ณธ์ธ ์‚ฌ์ดํŠธ์˜ ๋ฐ์ดํ„ฐ๋ฅผ ์ž„์˜๋กœ ๊ฐ€์ ธ๊ฐˆ ์ˆ˜ ์—†๋„๋ก

๋ฌธ์ œ โ€” ํ˜„๋Œ€ ์›น์˜ ํ˜„์‹ค:

  • Vue (localhost:3000) โ†” API (localhost:8080) โ†’ ๋‹ค๋ฅธ origin
  • SPA + REST API ๋ถ„๋ฆฌ ์‹œ ํ•ญ์ƒ ๋ฐœ์ƒ

CORS โ€” Same-Origin ์˜ˆ์™ธ ํ—ˆ์šฉ:

"์„œ๋ฒ„๊ฐ€ ํŠน์ • ๋‹ค๋ฅธ origin์˜ ์š”์ฒญ์„ ๋ช…์‹œ์ ์œผ๋กœ ํ—ˆ์šฉ"


CORS ํ๋ฆ„ โญ :

Simple Request (๋‹จ์ˆœ ์š”์ฒญ):

  • GET, HEAD, POST
  • ๊ธฐ๋ณธ ํ—ค๋”๋งŒ
  • โ†’ Preflight ์—†์ด ์ง์ ‘

Preflight Request (์‚ฌ์ „ ์š”์ฒญ):

  • PUT, DELETE, ์ปค์Šคํ…€ ํ—ค๋” ๋“ฑ
  • โ†’ OPTIONS ์š”์ฒญ ๋จผ์ €

Preflight ํ๋ฆ„:

1. [Browser] OPTIONS /api/users
   Origin: https://ilic.com
   Access-Control-Request-Method: PUT
   Access-Control-Request-Headers: Authorization
        โ†“
2. [Server] 200 OK
   Access-Control-Allow-Origin: https://ilic.com
   Access-Control-Allow-Methods: GET, POST, PUT, DELETE
   Access-Control-Allow-Headers: Authorization
   Access-Control-Allow-Credentials: true
        โ†“
3. [Browser] PUT /api/users (์‹ค์ œ ์š”์ฒญ)

Spring Security CORS ์„ค์ •:

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http
        .cors(cors -> cors.configurationSource(corsConfigurationSource()))
        // ...
    return http.build();
}

@Bean
public CorsConfigurationSource corsConfigurationSource() {
    CorsConfiguration config = new CorsConfiguration();
    config.setAllowedOrigins(List.of("https://ilic.com", "https://admin.ilic.com"));
    config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE"));
    config.setAllowedHeaders(List.of("*"));
    config.setAllowCredentials(true);  // Cookie ์ „์†ก ํ—ˆ์šฉ
    config.setMaxAge(3600L);  // Preflight ์บ์‹ฑ
    
    UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
    source.registerCorsConfiguration("/**", config);
    return source;
}

* ์™€์ด๋“œ์นด๋“œ vs ๋ช…์‹œ์  origin โš ๏ธ :

config.setAllowedOrigins(List.of("*"));     // โŒ ์œ„ํ—˜
config.setAllowedOrigins(List.of("https://ilic.com"));  // โœ… ์•ˆ์ „

allowCredentials=true ์ผ ๋•Œ * ์‚ฌ์šฉ ๋ถˆ๊ฐ€ (๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์ฐจ๋‹จ).


ILIC ์‹œ๋‚˜๋ฆฌ์˜ค:

  • ๊ฐœ๋ฐœ: localhost:3000 (Vue) โ†” localhost:8080 (Spring) โ†’ CORS ํ•„์š”
  • ์šด์˜: ilic.com โ†” api.ilic.com โ†’ CORS ํ•„์š”
  • ๋˜๋Š” ๊ฐ™์€ ๋„๋ฉ”์ธ์— reverse proxy (Nginx)๋กœ ๋ฐฐ์น˜ โ†’ CORS ๋ถˆํ•„์š”

์ž๊ธฐ ์ ๊ฒ€

  • CORS๋Š” ํด๋ผ์ด์–ธํŠธ ๋ณด์•ˆ์ธ๊ฐ€ ์„œ๋ฒ„ ๋ณด์•ˆ์ธ๊ฐ€? (ํžŒํŠธ: ๋‘˜ ๋‹ค โ€” ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ๊ฐ•์ œ + ์„œ๋ฒ„๊ฐ€ ํ—ˆ์šฉ)
  • ILIC๊ฐ€ CORS๋ฅผ ์•ˆ ์“ฐ๋ ค๋ฉด? (ํžŒํŠธ: ๊ฐ™์€ ๋„๋ฉ”์ธ + ๊ฒฝ๋กœ๋กœ ๋ถ„๋ฆฌ โ€” /api)

Unit 8.4 โ€” ์ข…ํ•ฉ ๋ณด์•ˆ ์ฒดํฌ๋ฆฌ์ŠคํŠธ

์„ ์ˆ˜ ์ง€์‹: Unit 8.1~8.3

ILIC ๋ณด์•ˆ ์ ๊ฒ€ ๋ฆฌ์ŠคํŠธ โญ :

์ธ์ฆ/์ธ๊ฐ€

  • BCrypt ์‚ฌ์šฉ (MD5/SHA-1 X)
  • ๋น„๋ฐ€๋ฒˆํ˜ธ ์ตœ์†Œ ๊ฐ•๋„ ๊ฒ€์ฆ
  • ๋กœ๊ทธ์ธ ์‹คํŒจ ํšŸ์ˆ˜ ์ œํ•œ (Brute Force ๋ฐฉ์–ด)
  • ๋น„๋ฐ€๋ฒˆํ˜ธ ๋ณ€๊ฒฝ ์‹œ ๋ชจ๋“  ํ† ํฐ ๋ฌดํšจํ™”

JWT

  • 256๋น„ํŠธ ์ด์ƒ ๋น„๋ฐ€ํ‚ค
  • HTTPS ํ•„์ˆ˜
  • HttpOnly Cookie ๋˜๋Š” Memory ์ €์žฅ
  • Access Token ์งง๊ฒŒ (15๋ถ„~1์‹œ๊ฐ„)
  • Refresh Token Rotation
  • Payload ๋ฏผ๊ฐ ์ •๋ณด X
  • ์•Œ๊ณ ๋ฆฌ์ฆ˜ ๋ช…์‹œ ๊ฒ€์ฆ

Web ์ทจ์•ฝ์ 

  • CSRF ๋ณดํ˜ธ (Session ์‹œ) ๋˜๋Š” SameSite
  • XSS ๋ฐฉ์–ด (์ถœ๋ ฅ ์ด์Šค์ผ€์ดํ”„, CSP)
  • CORS ๋ช…์‹œ์  origin (์™€์ผ๋“œ์นด๋“œ X)
  • HTTPS only
  • ๋ณด์•ˆ ํ—ค๋” (X-Frame-Options, X-Content-Type-Options ๋“ฑ)

SQL Injection (13์ฃผ์ฐจ)

  • PreparedStatement / JPA ํŒŒ๋ผ๋ฏธํ„ฐ ๋ฐ”์ธ๋”ฉ
  • ์ž…๋ ฅ ๊ฒ€์ฆ

์šด์˜

  • ์—๋Ÿฌ ๋ฉ”์‹œ์ง€์— ์‹œ์Šคํ…œ ์ •๋ณด ๋…ธ์ถœ X
  • ๋กœ๊ทธ์— ๋ฏผ๊ฐ ์ •๋ณด X
  • ์ •๊ธฐ ์˜์กด์„ฑ ์—…๋ฐ์ดํŠธ (CVE ์ถ”์ )
  • ์นจ์ž… ํƒ์ง€ (๋ชจ๋‹ˆํ„ฐ๋ง)

์ž๊ธฐ ์ ๊ฒ€

  • ILIC์˜ ํ˜„์žฌ ๋ณด์•ˆ ์ƒํƒœ๋ฅผ ์œ„ ์ฒดํฌ๋ฆฌ์ŠคํŠธ๋กœ ํ‰๊ฐ€ํ•˜๋ผ
  • "๊ฐ€์žฅ ์‹œ๊ธ‰ํ•œ ๋ณด์•ˆ ๊ฐœ์„  3๊ฐ€์ง€"๋ฅผ ์„ ์–ธํ•˜๋ผ

๐ŸŽ“ ์ข…ํ•ฉ ์ž๊ธฐ ์ ๊ฒ€ (18์ฃผ์ฐจ ์กธ์—… ์‹œํ—˜)

์ธ์ฆ/์ธ๊ฐ€ ๋ณธ์งˆ

  1. Authentication๊ณผ Authorization์˜ ์ฐจ์ด๋ฅผ ํ•œ ๋ฌธ์žฅ์”ฉ์œผ๋กœ?
  2. 401๊ณผ 403์˜ ์ฐจ์ด๋Š”?
  3. ์ธ์ฆ ๋ฐฉ์‹ 4๊ฐ€์ง€์˜ ์ง„ํ™”๋ฅผ ์„ค๋ช…ํ•˜๋ผ

Spring Security ์•„ํ‚คํ…์ฒ˜

  1. Spring Security๊ฐ€ ์–ด๋””์— ์œ„์น˜ํ•˜๋Š”์ง€ ๊ทธ๋ฆผ์œผ๋กœ?
  2. DelegatingFilterProxy์˜ ์—ญํ• ์€?
  3. SecurityFilterChain์˜ ํ•ต์‹ฌ Filter 5๊ฐœ์™€ ๊ฐ ์—ญํ• ์€?
  4. SecurityContextHolder๊ฐ€ ThreadLocal์ธ ์ด์œ ๋Š”?

์ธ์ฆ ์ฒ˜๋ฆฌ

  1. UserDetails์™€ UserDetailsService์˜ ๋ถ„๋ฆฌ ์ด์œ ๋Š”?
  2. AuthenticationManager โ†’ AuthenticationProvider ํ๋ฆ„์€?
  3. BCrypt๊ฐ€ MD5๋ณด๋‹ค ์•ˆ์ „ํ•œ 2๊ฐ€์ง€ ์ด์œ ๋Š”?

์ธ๊ฐ€ ์ฒ˜๋ฆฌ

  1. URL ์ธ๊ฐ€์™€ ๋ฉ”์„œ๋“œ ์ธ๊ฐ€์˜ ์ฐจ์ด๋Š”?
  2. Role๊ณผ Authority์˜ ์ฐจ์ด๋Š”?
  3. @PreAuthorize์™€ @PostAuthorize์˜ ์ฐจ์ด๋Š”?

Session vs Token (โ˜…)

  1. Session๊ณผ JWT์˜ ๊ฒฐ์ •์  ์ฐจ์ด๋ฅผ 5๊ฐ€์ง€ ๋น„๊ตํ•˜๋ผ
  2. Stateless์˜ ์žฅ๋‹จ์ ์€?
  3. ILIC๋Š” ์–ด๋–ค ๋ฐฉ์‹์ด ์ ํ•ฉํ•˜๊ณ  ์™œ?

JWT

  1. JWT 3๋ถ€๋ถ„(Header, Payload, Signature)์˜ ์—ญํ• ์€?
  2. Payload์— ๋ฏผ๊ฐ ์ •๋ณด๋ฅผ ๋„ฃ์œผ๋ฉด ์•ˆ ๋˜๋Š” ์ด์œ ๋Š”?
  3. HS256๊ณผ RS256์˜ ์ฐจ์ด์™€ MSA์—์„œ์˜ ์„ ํƒ์€?
  4. Access Token๊ณผ Refresh Token์˜ ํŒจํ„ด์„ ์„ค๋ช…ํ•˜๋ผ
  5. JWT ๋ณด์•ˆ ์ทจ์•ฝ์  5๊ฐ€์ง€์™€ ๋ฐฉ์–ด๋Š”?

OAuth2/OIDC

  1. OAuth2์˜ 4๊ฐ€์ง€ ์—ญํ• ์€?
  2. Authorization Code Grant ํ๋ฆ„์„ ์„ค๋ช…ํ•˜๋ผ
  3. OIDC๊ฐ€ OAuth2 ์œ„์— ์ถ”๊ฐ€ํ•˜๋Š” ๊ฒƒ์€?
  4. ID Token๊ณผ Access Token์˜ ์ฐจ์ด๋Š”?

์›น ๋ณด์•ˆ

  1. CSRF์˜ ์›๋ฆฌ์™€ ๋ฐฉ์–ด 3๊ฐ€์ง€๋Š”?
  2. XSS์˜ 3๊ฐ€์ง€ ์œ ํ˜•๊ณผ ๋ฐฉ์–ด๋Š”?
  3. CORS์˜ Preflight ํ๋ฆ„์€?
  4. JWT๋ฅผ LocalStorage vs HttpOnly Cookie ์–ด๋””์—?

๋ฉด์ ‘ ๋ชจ์˜ ๋‹ต๋ณ€ (์‹ค์ „)

  1. "Spring Security์˜ ๋™์ž‘ ์›๋ฆฌ๋ฅผ ์„ค๋ช…ํ•ด์ฃผ์„ธ์š”" (5๋ถ„)
  2. "Session๊ณผ JWT ์ค‘ ์–ด๋А ๊ฒƒ์„ ์„ ํ˜ธํ•˜์‹œ๋‚˜์š”? ์™œ์ฃ ?" (3๋ถ„)
  3. "JWT ๊ตฌํ˜„ ์‹œ ๊ฐ€์žฅ ์‹ ๊ฒฝ ์“ด ๋ณด์•ˆ์€?" (3๋ถ„)
  4. "OAuth2๋ฅผ ์ง์ ‘ ๊ตฌํ˜„ํ•ด ๋ณด์…จ๋‚˜์š”?" (2๋ถ„)

๐Ÿ“Œ ํ•™์Šต ์šด์˜ ํŒ

9-์„น์…˜ ๋งˆ์Šคํ„ฐ ํ”„๋กฌํ”„ํŠธ๋กœ ๊นŠ์ด ํŒŒ์•ผ ํ•  Unit

โ˜…โ˜…โ˜… ๋ฉด์ ‘ ๋‹จ๊ณจ (๋ฐ˜๋“œ์‹œ):

  • Unit 1.1 โ€” Authentication vs Authorization
  • Unit 2.2 โ€” SecurityFilterChain
  • Unit 3.2 โ€” AuthenticationManager + BCrypt
  • Unit 5.3 โ€” Session vs JWT ๋น„๊ต (โ˜… ๋ฉด์ ‘ ํ•ต์‹ฌ)
  • Unit 6.1 โ€” JWT ๊ตฌ์กฐ
  • Unit 6.4 โ€” JWT ๋ณด์•ˆ ์ทจ์•ฝ์ 
  • Unit 8.1 โ€” CSRF
  • Unit 8.3 โ€” CORS

โ˜…โ˜… ๋งค์šฐ ๊ถŒ์žฅ:

  • Unit 4.2 โ€” @PreAuthorize
  • Unit 6.3 โ€” Refresh Token ์ „๋žต
  • Unit 7.2 โ€” Authorization Code Grant
  • Unit 8.4 โ€” ๋ณด์•ˆ ์ฒดํฌ๋ฆฌ์ŠคํŠธ

๋‘ ์ •์ 

Phase 2 (Filter Chain):

  • 15์ฃผ์ฐจ Filter ํ•™์Šต์˜ ์ง„์งœ ๋ฌด๋Œ€
  • ๋ฉด์ ‘์—์„œ "Spring Security ๋™์ž‘ ์›๋ฆฌ?" ์งˆ๋ฌธ 100% ์ถœ์ œ
  • ์ง์ ‘ ๋””๋ฒ„๊ฑฐ๋กœ step-through ํ•˜๋ฉด ์˜์›ํžˆ ์žŠ์ง€ ์•Š์Œ

Phase 5 (Session vs Token):

  • ๋ฉด์ ‘์—์„œ ๊ฐ€์žฅ ์ž์ฃผ ๋‚˜์˜ค๋Š” ๋น„๊ต ๋ฌธ์ œ
  • ๋ณธ์ธ ์˜๊ฒฌ + ILIC ์‚ฌ๋ก€๋กœ ๋‹ต๋ณ€ ๊ฐ€๋Šฅํ•ด์•ผ ํ•จ

ํ•™์Šต ์‹œ ์ฃผ์˜ โ€” ์ง์ ‘ ๊ตฌํ˜„์ด ํ•ต์‹ฌ

์ด๋ฒˆ ์ฃผ์ฐจ๋Š” ๋ฐ˜๋“œ์‹œ ์ž‘์€ ํ”„๋กœ์ ํŠธ๋ฅผ ์ง์ ‘ ๋งŒ๋“ค์–ด๋ณด์„ธ์š”:

  1. Spring Boot + JWT ์ธ์ฆ ๋ฏธ๋‹ˆ ํ”„๋กœ์ ํŠธ:

    • ํšŒ์›๊ฐ€์ž… + ๋กœ๊ทธ์ธ (BCrypt)
    • JWT ๋ฐœ๊ธ‰/๊ฒ€์ฆ Filter
    • Access + Refresh Token
    • @PreAuthorize ๋ฉ”์„œ๋“œ ๋ณด์•ˆ
  2. OAuth2 ํ†ตํ•ฉ:

    • Google ๋กœ๊ทธ์ธ ์ถ”๊ฐ€
    • ์‚ฌ์šฉ์ž ์ž๋™ ๋“ฑ๋ก
  3. ๋ณด์•ˆ ์ทจ์•ฝ์  ์‹ค์Šต:

    • CSRF ๊ณต๊ฒฉ ์‹œ๋ฎฌ๋ ˆ์ด์…˜ (CSRF ๋ˆ ์ƒํƒœ)
    • XSS ๊ณต๊ฒฉ ์‹œ๋ฎฌ๋ ˆ์ด์…˜
    • CORS ์—๋Ÿฌ ์žฌํ˜„ + ํ•ด๊ฒฐ

์ด 3๊ฐ€์ง€๋ฅผ ๊ฑฐ์น˜๋ฉด ๋ฉด์ ‘ ๋‹ต๋ณ€์ด ์ž์—ฐ์Šค๋Ÿฌ์›Œ์ง‘๋‹ˆ๋‹ค.

1~18์ฃผ์ฐจ ํ•™์Šต ์—ฌ์ •

์ด์ œ ๋งˆ๋ฌด๋ฆฌ ๋‹จ๊ณ„๋กœ ๊ฐ€๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค:

์˜์—ญ์ฃผ์ฐจ๊นŠ์ด
Java/Spring/JPA1-12โ˜…โ˜…โ˜…
DB13-14โ˜…โ˜…โ˜…
Spring MVC15โ˜…โ˜…โ˜…
๋ถ„์‚ฐ ์‹œ์Šคํ…œ16-17โ˜…โ˜…โ˜…
Spring Security18โ˜…โ˜…โ˜…

profile
Software Developer

0๊ฐœ์˜ ๋Œ“๊ธ€